Imported Upstream version 4.8.3 upstream upstream/4.8.3
authorTizenOpenSource <tizenopensrc@samsung.com>
Thu, 7 Dec 2023 03:49:27 +0000 (12:49 +0900)
committerTizenOpenSource <tizenopensrc@samsung.com>
Thu, 7 Dec 2023 03:49:27 +0000 (12:49 +0900)
138 files changed:
.clang-format
.github/workflows/build.yaml
.github/workflows/codeql-analysis.yaml
.mailmap
CMakeLists.txt
CMakeSettings.json [new file with mode: 0644]
LICENSE.adoc
README.md
ci/build-macos-binary [new file with mode: 0755]
cmake/CcacheVersion.cmake
cmake/DevModeWarnings.cmake
cmake/Findhiredis.cmake
cmake/Findzstd.cmake
cmake/GenerateConfigurationFile.cmake
cmake/StandardSettings.cmake
cmake/config.h.in
doc/AUTHORS.adoc
doc/INSTALL.md
doc/MANUAL.adoc
doc/NEWS.adoc
misc/clang-format
misc/download-redis
misc/typos.toml [new file with mode: 0644]
misc/upload-redis
src/.clang-tidy
src/Args.cpp
src/AtomicFile.cpp
src/AtomicFile.hpp
src/CMakeLists.txt
src/Config.cpp
src/Config.hpp
src/Context.cpp
src/Context.hpp
src/File.hpp
src/InodeCache.cpp
src/InodeCache.hpp
src/ProgressBar.cpp
src/SignalHandler.cpp
src/SignalHandler.hpp
src/Stat.cpp
src/Stat.hpp
src/Util.cpp
src/Util.hpp
src/argprocessing.cpp
src/ccache.cpp
src/ccache.hpp
src/compopt.cpp
src/core/CMakeLists.txt
src/core/CacheEntry.cpp
src/core/FileRecompressor.cpp [new file with mode: 0644]
src/core/FileRecompressor.hpp [moved from src/storage/local/CacheFile.hpp with 58% similarity]
src/core/Manifest.cpp
src/core/ResultRetriever.cpp
src/core/Sloppiness.hpp
src/core/Statistic.hpp
src/core/Statistics.cpp
src/core/StatisticsCounters.cpp
src/core/StatisticsCounters.hpp
src/core/mainoptions.cpp
src/execute.cpp
src/hashutil.cpp
src/hashutil.hpp
src/storage/Storage.cpp
src/storage/local/CMakeLists.txt
src/storage/local/LocalStorage.cpp
src/storage/local/LocalStorage.hpp
src/storage/local/LocalStorage_cleanup.cpp [deleted file]
src/storage/local/LocalStorage_compress.cpp [deleted file]
src/storage/local/LocalStorage_statistics.cpp [deleted file]
src/storage/local/StatsFile.cpp
src/storage/local/StatsFile.hpp
src/storage/local/util.cpp
src/storage/local/util.hpp
src/storage/remote/FileStorage.cpp
src/test_lockfile.cpp
src/third_party/blake3/CMakeLists.txt
src/third_party/blake3/blake3.c
src/third_party/blake3/blake3.h
src/third_party/blake3/blake3_avx2.c
src/third_party/blake3/blake3_avx2_x86-64_windows_gnu.S
src/third_party/blake3/blake3_avx512.c
src/third_party/blake3/blake3_avx512_x86-64_windows_gnu.S
src/third_party/blake3/blake3_dispatch.c
src/third_party/blake3/blake3_impl.h
src/third_party/blake3/blake3_sse2.c
src/third_party/blake3/blake3_sse2_x86-64_windows_gnu.S
src/third_party/blake3/blake3_sse41.c
src/third_party/blake3/blake3_sse41_x86-64_windows_gnu.S
src/third_party/doctest.h
src/third_party/httplib.cpp
src/third_party/httplib.h
src/third_party/xxh_x86dispatch.c
src/third_party/xxh_x86dispatch.h
src/third_party/xxhash.c
src/third_party/xxhash.h
src/util/BitSet.hpp [new file with mode: 0644]
src/util/Bytes.cpp
src/util/Bytes.hpp
src/util/CMakeLists.txt
src/util/LockFile.cpp
src/util/LockFile.hpp
src/util/LongLivedLockFileManager.cpp [new file with mode: 0644]
src/util/LongLivedLockFileManager.hpp [moved from src/storage/local/CacheFile.cpp with 53% similarity]
src/util/TextTable.cpp
src/util/TextTable.hpp
src/util/file.cpp
src/util/file.hpp
src/util/path.cpp
src/util/string.cpp
src/util/string.hpp
test/run
test/suites/base.bash
test/suites/cleanup.bash
test/suites/color_diagnostics.bash
test/suites/config.bash
test/suites/depend.bash
test/suites/direct.bash
test/suites/inode_cache.bash
test/suites/remote_file.bash
test/suites/remote_redis.bash
test/suites/remote_redis_unix.bash
unittest/.clang-tidy
unittest/CMakeLists.txt
unittest/test_Args.cpp
unittest/test_Config.cpp
unittest/test_InodeCache.cpp
unittest/test_Stat.cpp
unittest/test_Util.cpp
unittest/test_argprocessing.cpp
unittest/test_ccache.cpp
unittest/test_hashutil.cpp
unittest/test_storage_local_util.cpp
unittest/test_util_BitSet.cpp [new file with mode: 0644]
unittest/test_util_LockFile.cpp
unittest/test_util_TextTable.cpp
unittest/test_util_file.cpp
unittest/test_util_path.cpp
unittest/test_util_string.cpp

index 67f0665..3d4ec9c 100644 (file)
@@ -1,4 +1,4 @@
-# This configuration should work with Clang-Format 10 and higher.
+# This configuration should work with Clang-Format 11 and higher.
 ---
 Language: Cpp
 BasedOnStyle: LLVM
index daea64b..4cd2791 100644 (file)
@@ -54,6 +54,14 @@ jobs:
             version: "12"
 
           - os: ubuntu-22.04
+            compiler: clang
+            version: "13"
+
+          - os: ubuntu-22.04
+            compiler: clang
+            version: "14"
+
+          - os: ubuntu-22.04
             compiler: gcc
             version: "11"
 
@@ -67,7 +75,15 @@ jobs:
 
           - os: macOS-11
             compiler: xcode
-            version: "12.4"
+            version: "12.5.1"
+
+          - os: macOS-12
+            compiler: xcode
+            version: "13.4.1"
+
+          - os: macOS-12
+            compiler: xcode
+            version: "14.2"
     steps:
       - name: Install dependencies
         run: |
@@ -116,7 +132,7 @@ jobs:
           fi
 
       - name: Get source
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Build and test
         run: ci/build
@@ -130,7 +146,7 @@ jobs:
 
       - name: Upload testdir from failed tests
         if: failure()
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v3
         with:
           name: ${{ matrix.config.os }}-${{ matrix.config.compiler }}-${{ matrix.config.version }}-testdir.tar.xz
           path: testdir.tar.xz
@@ -181,7 +197,7 @@ jobs:
           fi
 
       - name: Get source
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Build and test
         run: ci/build
@@ -199,11 +215,31 @@ jobs:
 
       - name: Upload testdir from failed tests
         if: failure()
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v3
         with:
           name: ${{ matrix.config.sys}}-${{ matrix.config.env }}-${{ matrix.config.compiler }}-testdir.tar.xz
           path: testdir.tar.xz
 
+  build_macos_universal:
+    name: macOS universal binary
+    runs-on: macos-12
+    env:
+      CMAKE_GENERATOR: Ninja
+    steps:
+      - name: Get source
+        uses: actions/checkout@v3
+      - name: Install Dependencies
+        run: |
+          HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 \
+              brew install ninja
+      - name: Build macOS universal binary
+        run: ci/build-macos-binary
+      - name: Archive universal binary
+        uses: actions/upload-artifact@v3
+        with:
+          name: macOS-binary
+          path: build_universal/ccache
+
   specific_tests:
     name: ${{ matrix.config.name }}
     runs-on: ${{ matrix.config.os }}
@@ -399,7 +435,7 @@ jobs:
 
     steps:
       - name: Get source
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Install CUDA
         if: matrix.config.CUDA != ''
@@ -413,7 +449,7 @@ jobs:
 
       - name: Prepare Windows environment (Visual Studio)
         if: runner.os == 'Windows'
-        uses: ilammy/msvc-dev-cmd@v1.5.0
+        uses: ilammy/msvc-dev-cmd@v1.12.0
         with:
           arch: ${{ matrix.config.msvc_arch }}
 
@@ -468,7 +504,7 @@ jobs:
 
       - name: Upload testdir from failed tests
         if: failure() || steps.build-and-test.outcome == 'failure'
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v3
         with:
           name: ${{ matrix.config.name }} - testdir.tar.xz
           path: testdir.tar.xz
@@ -480,7 +516,7 @@ jobs:
       fail-fast: false
     steps:
       - name: Get source
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Run Clang-Format in check mode
         run: misc/format-files --all --check
@@ -492,7 +528,7 @@ jobs:
       fail-fast: false
     steps:
       - name: Get source
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Install codespell
         run: |
@@ -501,4 +537,4 @@ jobs:
           pip3 install codespell==2.1.0
 
       - name: Run codespell
-        run: codespell -q 7 -S ".git,build*,./src/third_party/*" -I misc/codespell-allowlist.txt
+        run: codespell -q 7 -S ".git,build*,./misc/typos.toml,./src/third_party/*" -I misc/codespell-allowlist.txt
index 5bd772b..99b46fa 100644 (file)
@@ -31,7 +31,7 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
       with:
         # We must fetch at least the immediate parents so that if this is
         # a pull request then we can checkout the head.
index 9006713..4151530 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -12,6 +12,7 @@ Doug Anderson <dianders@disordat.com>
 Erik Flodin <erik@ejohansson.se>
 Florin Trofin <florint@gmail.com>
 Hongli Lai <hongli@phusion.nl>
+Huang Qin Jing <huangqinjin@gmail.com>
 Jacob Young <jacobly0@users.noreply.github.com>
 Jonny Yu <yingshen.yu@gmail.com>
 Ka Ho Ng <khng300@gmail.com>
index 6407690..b94ddce 100644 (file)
@@ -66,11 +66,11 @@ if(ENABLE_IPO AND NOT MINGW)
   set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
 endif()
 
+include(CIBuildType)
+include(DefaultBuildType)
 include(UseFastestLinker)
 include(StandardSettings)
 include(StandardWarnings)
-include(CIBuildType)
-include(DefaultBuildType)
 
 #
 # Configuration
@@ -112,10 +112,11 @@ endif()
 #
 # Third party
 #
-option(ZSTD_FROM_INTERNET
-  "Download and use libzstd from the Internet if not available" ON)
-option(HIREDIS_FROM_INTERNET
-  "Download and use libhiredis from the Internet if not available" ON)
+set(ZSTD_FROM_INTERNET AUTO CACHE STRING "Download and use libzstd from the Internet")
+set_property(CACHE ZSTD_FROM_INTERNET PROPERTY STRINGS AUTO ON OFF)
+
+set(HIREDIS_FROM_INTERNET AUTO CACHE STRING "Download and use libhiredis from the Internet")
+set_property(CACHE HIREDIS_FROM_INTERNET PROPERTY STRINGS AUTO ON OFF)
 
 find_package(zstd 1.1.2 MODULE REQUIRED)
 
diff --git a/CMakeSettings.json b/CMakeSettings.json
new file mode 100644 (file)
index 0000000..94b5134
--- /dev/null
@@ -0,0 +1,86 @@
+{
+  "configurations": [
+    {
+      "name": "x64-Debug",
+      "generator": "Ninja",
+      "configurationType": "Debug",
+      "inheritEnvironments": [
+        "msvc_x64"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "x64-Release",
+      "generator": "Ninja",
+      "configurationType": "Release",
+      "inheritEnvironments": [
+        "msvc_x64"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "x64-RelWithDebInfo",
+      "generator": "Ninja",
+      "configurationType": "RelWithDebInfo",
+      "inheritEnvironments": [
+        "msvc_x64"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "x86-Debug",
+      "generator": "Ninja",
+      "configurationType": "Debug",
+      "inheritEnvironments": [
+        "msvc_x86"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "x86-Release",
+      "generator": "Ninja",
+      "configurationType": "Release",
+      "inheritEnvironments": [
+        "msvc_x86"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "x86-RelWithDebInfo",
+      "generator": "Ninja",
+      "configurationType": "RelWithDebInfo",
+      "inheritEnvironments": [
+        "msvc_x86"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "arm64-Debug",
+      "generator": "Ninja",
+      "configurationType": "Debug",
+      "inheritEnvironments": [
+        "msvc_arm64"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "arm64-Release",
+      "generator": "Ninja",
+      "configurationType": "Release",
+      "inheritEnvironments": [
+        "msvc_arm64"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }, {
+      "name": "arm64-RelWithDebInfo",
+      "generator": "Ninja",
+      "configurationType": "RelWithDebInfo",
+      "inheritEnvironments": [
+        "msvc_arm64"
+      ],
+      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
+      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}"
+    }
+  ]
+}
index b863cec..e016717 100644 (file)
@@ -35,7 +35,7 @@ The copyright for ccache as a whole is as follows:
 
 ----
 Copyright (C) 2002-2007 Andrew Tridgell
-Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 ----
 
 
@@ -72,8 +72,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 === src/third_party/blake3/blake3_*
 
-This is a subset of https://github.com/BLAKE3-team/BLAKE3[BLAKE3] 1.3.1 with
-the following license:
+This is a subset of https://github.com/BLAKE3-team/BLAKE3[BLAKE3] 1.4.0 with the
+following license:
 
 ----
 This work is released into the public domain with CC0 1.0. Alternatively, it is
@@ -412,7 +412,7 @@ express Statement of Purpose.
 === src/third_party/doctest.h
 
 This is the single header version of https://github.com/onqtam/doctest[doctest]
-2.4.8 with the following license:
+2.4.10 with the following license:
 
 ----
 The MIT License (MIT)
@@ -516,13 +516,13 @@ SUCH DAMAGE.
 === src/third_party/httplib.*
 
 cpp-httplib - A C++11 cross-platform HTTP/HTTPS library. Copied from cpp-httplib
-v0.11.1 downloaded from https://github.com/yhirose/cpp-httplib[cpp-httplib]. The
+v0.12.6 downloaded from https://github.com/yhirose/cpp-httplib[cpp-httplib]. The
 library has the following license:
 
 ----
 The MIT License (MIT)
 
-Copyright (c) 2022 yhirose
+Copyright (c) 2023 yhirose
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -771,7 +771,7 @@ xxHash - Extremely Fast Hash algorithm. Copied from xxHash v0.8.1 downloaded
 from <https://github.com/Cyan4973/xxHash/releases>.
 
 ----
-Copyright (c) 2012-2020 Yann Collet
+Copyright (c) 2012-2021 Yann Collet
 All rights reserved.
 
 BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
index efe4686..7053dd1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,9 +2,8 @@ Ccache â€“ a fast compiler cache
 ==============================
 
 [![Build Status](https://github.com/ccache/ccache/workflows/Build/badge.svg)](https://github.com/ccache/ccache/actions?query=workflow%3A%22Build%22)
-[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/cpp/g/ccache/ccache.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ccache/ccache/context:cpp)
-[![Total Alerts](https://img.shields.io/lgtm/alerts/g/ccache/ccache.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ccache/ccache/alerts)
 [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5057/badge)](https://bestpractices.coreinfrastructure.org/projects/5057)
+[![Discussions](https://img.shields.io/github/discussions/ccache/ccache)](discussions)
 [![Gitter](https://img.shields.io/gitter/room/ccache/ccache.svg)](https://gitter.im/ccache/ccache)
 
 Ccache is a compiler cache. It [speeds up
@@ -29,10 +28,10 @@ Contributing to ccache
 
 * [Source repository](https://github.com/ccache/ccache)
 * [Notes on how to contribute](https://github.com/ccache/ccache/blob/master/CONTRIBUTING.md)
+* [Discussions](https://github.com/ccache/ccache/discussions)
 * [Mailing list](https://lists.samba.org/mailman/listinfo/ccache/)
 * [Chat](https://gitter.im/ccache/ccache)
 * [Bug report info](https://ccache.dev/bugs.html)
-* [Discussions](https://github.com/ccache/ccache/discussions)
 * [Issue tracker](https://github.com/ccache/ccache/issues)
   * [Help wanted!](https://github.com/ccache/ccache/labels/help%20wanted)
   * [Good first issues!](https://github.com/ccache/ccache/labels/good%20first%20issue)
diff --git a/ci/build-macos-binary b/ci/build-macos-binary
new file mode 100755 (executable)
index 0000000..d26e511
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+set -eu
+
+build() {
+    mkdir -p "build_$1"
+    cd "build_$1"
+    cmake \
+        -DCMAKE_BUILD_TYPE=Release \
+        -DZSTD_FROM_INTERNET=ON \
+        -DHIREDIS_FROM_INTERNET=ON \
+        -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" \
+        -DCMAKE_OSX_ARCHITECTURES="$1" \
+        -DCMAKE_SYSTEM_PROCESSOR="$1" \
+        ..
+    cmake --build .
+    cd ..
+}
+
+FILES=""
+set -- x86_64 arm64
+
+for i in "$@"; do
+    echo "Building $i"
+    build "$i"
+    FILES="${FILES} build_$i/ccache"
+done
+
+mkdir -p build_universal
+lipo -create $FILES -output build_universal/ccache
+lipo -info build_universal/ccache
index 1181651..44592ea 100644 (file)
@@ -22,7 +22,7 @@
 # CCACHE_VERSION_ORIGIN is set to "archive" in scenario 1 and "git" in scenario
 # 3.
 
-set(version_info "1527040bc2a278b9d3d51badb732ecf5841d8bb5 HEAD, tag: v4.7.4, origin/master, origin/HEAD, master")
+set(version_info "044558e647b49dbc8fc89b810a7d22f526101e6b tag: v4.8.3, 4.8-maint")
 set(CCACHE_VERSION "unknown")
 
 if(version_info MATCHES "^([0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])[0-9a-f]* (.*)")
@@ -54,7 +54,7 @@ elseif(EXISTS "${CMAKE_SOURCE_DIR}/.git")
         ERROR_VARIABLE git_stderr ERROR_STRIP_TRAILING_WHITESPACE)
     endmacro()
 
-    git(describe --abbrev=8 --dirty)
+    git(describe --tags --abbrev=8 --dirty)
     if(git_stdout MATCHES "^v([^-]+)(-dirty)?$")
       set(CCACHE_VERSION "${CMAKE_MATCH_1}")
       if(NOT "${CMAKE_MATCH_2}" STREQUAL "")
index acc32b7..1db8135 100644 (file)
@@ -34,25 +34,23 @@ endmacro()
 
 set(
   _clang_gcc_warnings
+  -Wcast-align
+  -Wdouble-promotion
   -Wextra
   -Wnon-virtual-dtor
-  -Wcast-align
-  -Wunused
+  -Wnull-dereference
   -Woverloaded-virtual
   -Wpedantic
+  -Wshadow
+  -Wunused
 
   # Candidates for enabling in the future:
-  # -Wshadow
   # -Wold-style-cast
   # -Wconversion
   # -Wsign-conversion
-  # -Wnull-dereference
   # -Wformat=2
 )
 
-# Tested separately as this is not supported by Clang 3.4.
-add_compile_flag_if_supported(_clang_gcc_warnings "-Wdouble-promotion")
-
 if(WARNINGS_AS_ERRORS)
   list(APPEND _clang_gcc_warnings -Werror)
 endif()
@@ -76,6 +74,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
 
   # Disable C++20 compatibility for now.
   add_compile_flag_if_supported(CCACHE_COMPILER_WARNINGS "-Wno-c++2a-compat")
+  add_compile_flag_if_supported(CCACHE_COMPILER_WARNINGS "-Wno-c99-extensions")
+  add_compile_flag_if_supported(CCACHE_COMPILER_WARNINGS "-Wno-language-extension-token")
 
   # If compiler supports these warnings they have to be disabled for now.
   add_compile_flag_if_supported(
index b8045f6..894fd33 100644 (file)
@@ -2,54 +2,72 @@ if(hiredis_FOUND)
   return()
 endif()
 
+if(POLICY CMP0135)
+  # Set timestamps on extracted files to time of extraction.
+  cmake_policy(SET CMP0135 NEW)
+endif()
+
 set(hiredis_FOUND FALSE)
 
-find_package(PkgConfig)
-if(PKG_CONFIG_FOUND)
-  pkg_check_modules(HIREDIS hiredis>=${hiredis_FIND_VERSION})
-  find_library(HIREDIS_LIBRARY ${HIREDIS_LIBRARIES} HINTS ${HIREDIS_LIBDIR})
-  find_path(HIREDIS_INCLUDE_DIR hiredis/hiredis.h HINTS ${HIREDIS_PREFIX}/include)
+if(HIREDIS_FROM_INTERNET AND NOT HIREDIS_FROM_INTERNET STREQUAL "AUTO")
+  message(STATUS "Using hiredis from the Internet")
+  set(do_download TRUE)
 else()
-  find_library(HIREDIS_LIBRARY hiredis)
-  find_path(HIREDIS_INCLUDE_DIR hiredis/hiredis.h)
-endif()
-
-if(HIREDIS_INCLUDE_DIR AND HIREDIS_LIBRARY)
-  mark_as_advanced(HIREDIS_INCLUDE_DIR HIREDIS_LIBRARY)
+  find_package(PkgConfig)
+  if(PKG_CONFIG_FOUND)
+    pkg_check_modules(HIREDIS hiredis>=${hiredis_FIND_VERSION})
+    find_library(HIREDIS_LIBRARY ${HIREDIS_LIBRARIES} HINTS ${HIREDIS_LIBDIR})
+    find_path(HIREDIS_INCLUDE_DIR hiredis/hiredis.h HINTS ${HIREDIS_PREFIX}/include)
+    if(HIREDIS_LIBRARY AND HIREDIS_INCLUDE_DIR)
+      message(STATUS "Using hiredis from ${HIREDIS_LIBRARY} via pkg-config")
+      set(hiredis_FOUND TRUE)
+    endif()
+  endif()
 
-  add_library(HIREDIS::HIREDIS UNKNOWN IMPORTED)
-  set_target_properties(
-    HIREDIS::HIREDIS
-    PROPERTIES
-    IMPORTED_LOCATION "${HIREDIS_LIBRARY}"
-    INTERFACE_COMPILE_OPTIONS "${HIREDIS_CFLAGS_OTHER}"
-    INTERFACE_INCLUDE_DIRECTORIES "${HIREDIS_INCLUDE_DIR}")
-  if(WIN32 AND STATIC_LINK)
-    target_link_libraries(HIREDIS::HIREDIS INTERFACE ws2_32)
+  if(NOT hiredis_FOUND)
+    find_library(HIREDIS_LIBRARY hiredis)
+    find_path(HIREDIS_INCLUDE_DIR hiredis/hiredis.h)
+    if(HIREDIS_LIBRARY AND HIREDIS_INCLUDE_DIR)
+      message(STATUS "Using hiredis from ${HIREDIS_LIBRARY}")
+      set(hiredis_FOUND TRUE)
+    endif()
   endif()
 
-  set(hiredis_FOUND TRUE)
-  set(target HIREDIS::HIREDIS)
-elseif(HIREDIS_FROM_INTERNET)
-  message(STATUS "*** WARNING ***: Using hiredis from the internet because it was NOT found and HIREDIS_FROM_INTERNET is TRUE")
+  if(hiredis_FOUND)
+    mark_as_advanced(HIREDIS_INCLUDE_DIR HIREDIS_LIBRARY)
+    add_library(HIREDIS::HIREDIS UNKNOWN IMPORTED)
+    set_target_properties(
+      HIREDIS::HIREDIS
+      PROPERTIES
+      IMPORTED_LOCATION "${HIREDIS_LIBRARY}"
+      INTERFACE_COMPILE_OPTIONS "${HIREDIS_CFLAGS_OTHER}"
+      INTERFACE_INCLUDE_DIRECTORIES "${HIREDIS_INCLUDE_DIR}"
+    )
+    if(WIN32 AND STATIC_LINK)
+      target_link_libraries(HIREDIS::HIREDIS INTERFACE ws2_32)
+    endif()
+    set(hiredis_FOUND TRUE)
+    set(target HIREDIS::HIREDIS)
+  elseif(HIREDIS_FROM_INTERNET STREQUAL "AUTO")
+    message(STATUS "*** WARNING ***: Using hiredis from the Internet because it was not found and HIREDIS_FROM_INTERNET is AUTO")
+    set(do_download TRUE)
+  endif()
+endif()
 
+if(do_download)
   set(hiredis_version "1.1.0")
-
   set(hiredis_dir   ${CMAKE_BINARY_DIR}/hiredis-${hiredis_version})
   set(hiredis_build ${CMAKE_BINARY_DIR}/hiredis-build)
 
   include(FetchContent)
-
   FetchContent_Declare(
     hiredis
-    URL         https://github.com/redis/hiredis/archive/v${hiredis_version}.tar.gz
-    URL_HASH    SHA256=fe6d21741ec7f3fc9df409d921f47dfc73a4d8ff64f4ac6f1d95f951bf7f53d6
-    SOURCE_DIR  ${hiredis_dir}
-    BINARY_DIR  ${hiredis_build}
+    URL https://github.com/redis/hiredis/archive/refs/tags/v${hiredis_version}.tar.gz
+    SOURCE_DIR ${hiredis_dir}
+    BINARY_DIR ${hiredis_build}
   )
 
   FetchContent_GetProperties(hiredis)
-
   if(NOT hiredis_POPULATED)
     FetchContent_Populate(hiredis)
   endif()
@@ -88,7 +106,6 @@ endif()
 if(WIN32 AND hiredis_FOUND)
   target_link_libraries(${target} INTERFACE ws2_32)
 endif()
-unset(target)
 
 include(FeatureSummary)
 set_package_properties(
index a6d30a4..14aeda5 100644 (file)
@@ -2,52 +2,77 @@ if(zstd_FOUND)
   return()
 endif()
 
+if(POLICY CMP0135)
+  # Set timestamps on extracted files to time of extraction.
+  cmake_policy(SET CMP0135 NEW)
+endif()
+
 set(zstd_FOUND FALSE)
 
-find_package(PkgConfig)
-if(PKG_CONFIG_FOUND)
-  pkg_search_module(PC_ZSTD libzstd)
-  find_library(ZSTD_LIBRARY zstd HINTS ${PC_ZSTD_LIBDIR} ${PC_ZSTD_LIBRARY_DIRS})
-  find_path(ZSTD_INCLUDE_DIR zstd.h HINTS ${PC_ZSTD_INCLUDEDIR} ${PC_ZSTD_INCLUDE_DIRS})
+if(ZSTD_FROM_INTERNET AND NOT ZSTD_FROM_INTERNET STREQUAL "AUTO")
+  message(STATUS "Using zstd from the Internet")
+  set(do_download TRUE)
 else()
-  find_library(ZSTD_LIBRARY zstd)
-  find_path(ZSTD_INCLUDE_DIR zstd.h)
-endif()
-
-if(ZSTD_LIBRARY AND ZSTD_INCLUDE_DIR)
-  mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
+  find_package(PkgConfig)
+  if(PKG_CONFIG_FOUND)
+    pkg_search_module(PC_ZSTD libzstd)
+    find_library(ZSTD_LIBRARY zstd HINTS ${PC_ZSTD_LIBDIR} ${PC_ZSTD_LIBRARY_DIRS})
+    find_path(ZSTD_INCLUDE_DIR zstd.h HINTS ${PC_ZSTD_INCLUDEDIR} ${PC_ZSTD_INCLUDE_DIRS})
+    if(ZSTD_LIBRARY AND ZSTD_INCLUDE_DIR)
+      message(STATUS "Using zstd from ${ZSTD_LIBRARY} via pkg-config")
+      set(zstd_FOUND TRUE)
+    endif()
+  endif()
 
-  add_library(ZSTD::ZSTD UNKNOWN IMPORTED)
-  set_target_properties(
-    ZSTD::ZSTD
-    PROPERTIES
-    IMPORTED_LOCATION "${ZSTD_LIBRARY}"
-    INTERFACE_INCLUDE_DIRECTORIES "${ZSTD_INCLUDE_DIR}")
+  if(NOT zstd_FOUND)
+    find_library(ZSTD_LIBRARY zstd)
+    find_path(ZSTD_INCLUDE_DIR zstd.h)
+    if(ZSTD_LIBRARY AND ZSTD_INCLUDE_DIR)
+      message(STATUS "Using zstd from ${ZSTD_LIBRARY}")
+      set(zstd_FOUND TRUE)
+    endif()
+  endif()
 
-  set(zstd_FOUND TRUE)
-elseif(ZSTD_FROM_INTERNET)
-  message(STATUS "*** WARNING ***: Using zstd from the internet because it was NOT found and ZSTD_FROM_INTERNET is TRUE")
+  if(zstd_FOUND)
+    mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
+    add_library(ZSTD::ZSTD UNKNOWN IMPORTED)
+    set_target_properties(
+      ZSTD::ZSTD
+      PROPERTIES
+      IMPORTED_LOCATION "${ZSTD_LIBRARY}"
+      INTERFACE_INCLUDE_DIRECTORIES "${ZSTD_INCLUDE_DIR}"
+    )
+    set(zstd_FOUND TRUE)
+  elseif(ZSTD_FROM_INTERNET STREQUAL "AUTO")
+    message(STATUS "*** WARNING ***: Using zstd from the Internet because it was not found and ZSTD_FROM_INTERNET is AUTO")
+    set(do_download TRUE)
+  endif()
+endif()
 
+if(do_download)
   # Although ${zstd_FIND_VERSION} was requested, let's download a newer version.
   # Note: The directory structure has changed in 1.3.0; we only support 1.3.0
   # and newer.
-  set(zstd_version "1.5.2")
-
+  set(zstd_version "1.5.5")
   set(zstd_dir   ${CMAKE_BINARY_DIR}/zstd-${zstd_version})
   set(zstd_build ${CMAKE_BINARY_DIR}/zstd-build)
 
-  include(FetchContent)
+  if(XCODE)
+    # See https://github.com/facebook/zstd/pull/3665
+    set(zstd_patch PATCH_COMMAND sed -i .bak -e s/^set_source_files_properties.*PROPERTIES.*LANGUAGE.*C/\#&/ build/cmake/lib/CMakeLists.txt)
+  endif()
 
+  include(FetchContent)
   FetchContent_Declare(
     zstd
-    URL         https://github.com/facebook/zstd/archive/v${zstd_version}.tar.gz
-    URL_HASH    SHA256=f7de13462f7a82c29ab865820149e778cbfe01087b3a55b5332707abf9db4a6e
-    SOURCE_DIR  ${zstd_dir}
-    BINARY_DIR  ${zstd_build}
+    URL https://github.com/facebook/zstd/releases/download/v${zstd_version}/zstd-${zstd_version}.tar.gz
+    URL_HASH SHA256=9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4
+    SOURCE_DIR ${zstd_dir}
+    BINARY_DIR ${zstd_build}
+    ${zstd_patch}
   )
 
   FetchContent_GetProperties(zstd)
-
   if(NOT zstd_POPULATED)
     FetchContent_Populate(zstd)
   endif()
@@ -70,4 +95,5 @@ set_package_properties(
   zstd
   PROPERTIES
   URL "https://facebook.github.io/zstd"
-  DESCRIPTION "Zstandard - Fast real-time compression algorithm")
+  DESCRIPTION "Zstandard - Fast real-time compression algorithm"
+)
index 858567b..4fac7b0 100644 (file)
@@ -43,22 +43,6 @@ foreach(func IN ITEMS ${functions})
   check_function_exists(${func} ${func_var})
 endforeach()
 
-include(CheckCXXSourceCompiles)
-set(CMAKE_REQUIRED_FLAGS -pthread)
-check_cxx_source_compiles(
-  [=[
-    #include <pthread.h>
-    int main()
-    {
-      pthread_mutexattr_t attr;
-      (void)pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
-      return 0;
-    }
-  ]=]
-  HAVE_PTHREAD_MUTEX_ROBUST)
-check_function_exists(pthread_mutexattr_setpshared HAVE_PTHREAD_MUTEXATTR_SETPSHARED)
-set(CMAKE_REQUIRED_FLAGS)
-
 include(CheckStructHasMember)
 check_struct_has_member("struct stat" st_atim sys/stat.h
                         HAVE_STRUCT_STAT_ST_ATIM LANGUAGE CXX)
@@ -103,7 +87,6 @@ endif()
 set(MTR_ENABLED "${ENABLE_TRACING}")
 
 if(HAVE_SYS_MMAN_H
-   AND HAVE_PTHREAD_MUTEXATTR_SETPSHARED
    AND (HAVE_STRUCT_STAT_ST_MTIM OR HAVE_STRUCT_STAT_ST_MTIMESPEC)
    AND (HAVE_LINUX_FS_H OR HAVE_STRUCT_STATFS_F_FSTYPENAME))
   set(INODE_CACHE_SUPPORTED 1)
index 9e09ea6..b6a0643 100644 (file)
@@ -12,7 +12,11 @@ else()
   )
 endif()
 
-if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$")
+if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$" AND NOT MSVC)
+  if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+    target_compile_definitions(standard_settings INTERFACE _GLIBCXX_ASSERTIONS)
+  endif()
+
   option(ENABLE_COVERAGE "Enable coverage reporting for GCC/Clang" FALSE)
   if(ENABLE_COVERAGE)
     target_compile_options(standard_settings INTERFACE --coverage -O0 -g)
@@ -55,17 +59,16 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$")
 
   include(StdAtomic)
   include(StdFilesystem)
-
-elseif(MSVC)
+elseif(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "^Clang$")
   target_compile_options(
     standard_settings
-    INTERFACE /Zc:preprocessor /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS
+    INTERFACE /Zc:preprocessor /Zc:__cplusplus
   )
 endif()
 
 if(WIN32)
   target_compile_definitions(
     standard_settings
-    INTERFACE WIN32_LEAN_AND_MEAN
+    INTERFACE WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS
   )
 endif()
index 1537119..2778909 100644 (file)
@@ -91,9 +91,6 @@
 // Define if you have the "posix_fallocate.
 #cmakedefine HAVE_POSIX_FALLOCATE
 
-// Define if you have the "pthread_mutexattr_setpshared" function.
-#cmakedefine HAVE_PTHREAD_MUTEXATTR_SETPSHARED
-
 // Define if you have the <pwd.h> header file.
 #cmakedefine HAVE_PWD_H
 
 // Define if you have the "utimes" function.
 #cmakedefine HAVE_UTIMES
 
-// Define if you have the "PTHREAD_MUTEX_ROBUST" constant.
-#cmakedefine HAVE_PTHREAD_MUTEX_ROBUST
-
 #if defined(__ibmxl__) && defined(__clang__) // Compiler xlclang
 #  undef HAVE_VARARGS_H // varargs.h would hide macros of stdarg.h
 #  undef HAVE_STRUCT_STAT_ST_CTIM
index 8d8f12c..665143f 100644 (file)
@@ -19,6 +19,7 @@ Ccache is a collective work with contributions from many people, including:
 * Andreas Huber
 * André Klitzing
 * Andrew Boie
+* Andrew Hardin
 * Andrew Stubbs
 * Andrew Tridgell
 * Arne Hasselbring
@@ -50,11 +51,13 @@ Ccache is a collective work with contributions from many people, including:
 * Harsh Shandilya
 * Havard Graff
 * Hongli Lai
+* Huang Qin Jing
 * Igor Pylypiv
 * Ivan Vaigult
 * Ivan Volnov
 * Jacob Young
 * Jiang Jiang
+* Jiri Hörner
 * Joel Galenson
 * Joel Rosdahl
 * John Basila
@@ -134,9 +137,11 @@ Ccache is a collective work with contributions from many people, including:
 * Ryan Burns
 * Ryan Egesdahl
 * Sam Gross
+* Sam James
 * Sergei Trofimovich
 * Sergey Semushin
 * Steffen Dettmer
+* Stephan Rohmen
 * Steve Mokris
 * Stuart Henderson
 * Sumit Jamgade
@@ -144,6 +149,7 @@ Ccache is a collective work with contributions from many people, including:
 * Thomas Röfer
 * Timofei Kushnir
 * Tim Potter
+* Tobias Hieta
 * Tomasz MiÄ…sko
 * Tom Hughes
 * Tom Stellard
index e076387..920cb4d 100644 (file)
@@ -14,9 +14,9 @@ To build ccache you need:
 - [libzstd](http://www.zstd.net). If you don't have libzstd installed and can't
   or don't want to install it in a standard system location, it will be
   automatically downloaded, built and linked statically as part of the build
-  process. To disable this, pass `-DZSTD_FROM_INTERNET=OFF` to `cmake`. You can
-  also install zstd in a custom path and pass
-  `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`.
+  process. To disable this, pass `-DZSTD_FROM_INTERNET=OFF` to `cmake`, or pass
+  `-DZSTD_FROM_INTERNET=ON` to force downloading. You can also install zstd in a
+  custom path and pass `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`.
 
   To link libzstd statically (and you have a static libzstd available), pass
   `-DSTATIC_LINK=ON` to `cmake`. This is the default on Windows. Alternatively,
@@ -28,8 +28,9 @@ Optional:
   you don't have libhiredis installed and can't or don't want to install it in a
   standard system location, it will be automatically downloaded, built and
   linked statically as part of the build process. To disable this, pass
-  `-DHIREDIS_FROM_INTERNET=OFF` to cmake. You can also install hiredis in a
-  custom path and pass `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`.
+  `-DHIREDIS_FROM_INTERNET=OFF` to `cmake`, or pass `-DHIREDIS_FROM_INTERNET=ON`
+  to force downloading.. You can also install hiredis in a custom path and pass
+  `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`.
 
   To link libhiredis statically (and you have a static libhiredis available),
   pass `-DSTATIC_LINK=ON` to `cmake`. This is the default on Windows.
index 48328fa..28922e0 100644 (file)
@@ -9,9 +9,14 @@ ccache - a fast C/C++ compiler cache
 == Synopsis
 
 [verse]
-*ccache* [_options_]
-*ccache* _compiler_ [_compiler options_]
-_compiler_ [_compiler options_]            (ccache masquerading as the compiler)
+*ccache* [_ccache options_]
+*ccache* [_KEY_=_VALUE_ ...] _compiler_ [_compiler options_]
+_compiler_ [_compiler options_]
+
+The first form takes options described in <<Command line options>> below. The
+second form invokes the compiler, optionally using <<Configuration,configuration
+options>> as _KEY_=_VALUE_ arguments. In the third form, ccache is masquerading
+as the compiler as described in <<Run modes>>.
 
 
 == Description
@@ -79,13 +84,14 @@ documentation.
 
 *-c*, *--cleanup*::
 
-    Clean up the cache by removing old cached files until the specified file
-    number and cache size limits are not exceeded. This also recalculates the
-    cache file count and size totals. Normally, there is no need to initiate
-    cleanup manually as ccache keeps the cache below the specified limits at
-    runtime and keeps statistics up to date on each compilation. Forcing a
-    cleanup is mostly useful if you manually modify the cache contents or
-    believe that the cache size statistics may be inaccurate.
+    Clean up the cache by removing not recently used cached files until the
+    specified file number and cache size limits are not exceeded. This also
+    recalculates the cache file count and size totals. Normally, there is no
+    need to initiate cleanup manually as ccache keeps the cache below the
+    specified limits at runtime and keeps statistics up to date on each
+    compilation. Forcing a cleanup is mostly useful if you have modified the
+    cache contents manually or believe that the cache size statistics may be
+    inaccurate.
 
 *-C*, *--clear*::
 
@@ -113,9 +119,9 @@ documentation.
 
 *--evict-older-than* _AGE_::
 
-    Remove files older than _AGE_ from the cache. _AGE_ should be an unsigned
-    integer with a `d` (days) or `s` (seconds) suffix. If combined with
-    `--evict-namespace`, only remove old files within that namespace.
+    Remove files used less recently than _AGE_ from the cache. _AGE_ should be
+    an unsigned integer with a `d` (days) or `s` (seconds) suffix. If combined
+    with `--evict-namespace`, only remove files within that namespace.
 
 *-h*, *--help*::
 
@@ -130,22 +136,27 @@ documentation.
 *-M* _SIZE_, *--max-size* _SIZE_::
 
     Set the maximum size of the files stored in the cache. _SIZE_ should be a
-    number followed by an optional suffix: k, M, G, T (decimal), Ki, Mi, Gi or
-    Ti (binary). The default suffix is G. Use 0 for no limit. The value is
-    stored in a configuration file in the cache directory and applies to all
-    future compilations.
+    number followed by an optional suffix: kB, MB, GB, TB (decimal), KiB, MiB,
+    GiB or TiB (binary). The default suffix is GiB. Use 0 for no limit. The
+    value is stored in a configuration file in the cache directory and applies
+    to all future compilations.
 
 *-X* _LEVEL_, *--recompress* _LEVEL_::
 
     Recompress the cache to level _LEVEL_ using the Zstandard algorithm. The
     level can be an integer, with the same semantics as the
     <<config_compression_level,*compression_level*>> configuration option, or
-    the special value *uncompressed* for no compression. See
-    _<<Cache compression>>_ for more information. This can potentionally take a
-    long time since all files in the cache need to be visited. Only files that
-    are currently compressed with a different level than _LEVEL_ will be
+    the special value *uncompressed* for no compression. See _<<Cache
+    compression>>_ for more information. This can potentially take a long time
+    since all files in the cache need to be visited. Only files that are
+    currently compressed with a different level than _LEVEL_ will be
     recompressed.
 
+*--recompress-threads* _THREADS_::
+
+    Use up to _THREADS_ threads when recompressing the cache. The default is to
+    use one thread per CPU.
+
 *-o* _KEY=VALUE_, *--set-config* _KEY_=_VALUE_::
 
     Set configuration option _KEY_ to _VALUE_. See _<<Configuration>>_ for more
@@ -154,7 +165,7 @@ documentation.
 *-x*, *--show-compression*::
 
     Print cache compression statistics. See _<<Cache compression>>_ for more
-    information. This can potentionally take a long time since all files in the
+    information. This can potentially take a long time since all files in the
     cache need to be visited.
 
 *-p*, *--show-config*::
@@ -191,8 +202,8 @@ documentation.
 
 *--trim-dir* _PATH_::
 
-    Remove old files from directory _PATH_ until it is at most the size
-    specified by `--trim-max-size`.
+    Remove not recently used files from directory _PATH_ until it is at most the
+    size specified by `--trim-max-size`.
 +
 WARNING: Don't use this option to trim the local cache. To trim the local cache
 directory to a certain size, use `CCACHE_MAXSIZE=_SIZE_ ccache -c`.
@@ -200,8 +211,8 @@ directory to a certain size, use `CCACHE_MAXSIZE=_SIZE_ ccache -c`.
 *--trim-max-size* _SIZE_::
 
     Specify the maximum size for `--trim-dir`. _SIZE_ should be a number
-    followed by an optional suffix: k, M, G, T (decimal), Ki, Mi, Gi or Ti
-    (binary). The default suffix is G.
+    followed by an optional suffix: kB, MB, GB, TB (decimal), KiB, MiB, GiB or
+    TiB (binary). The default suffix is GiB. Use 0 for no limit.
 
 *--trim-method* _METHOD_::
 
@@ -216,6 +227,23 @@ directory to a certain size, use `CCACHE_MAXSIZE=_SIZE_ ccache -c`.
     LRU (least recently used) using the file modification timestamp.
 --
 
+*--trim-recompress* _LEVEL_::
+
+    Recompress to level _LEVEL_ using the Zstandard algorithm when using
+    `--trim-dir`. The level can be an integer, with the same semantics as the
+    <<config_compression_level,*compression_level*>> configuration option, or
+    the special value *uncompressed* for no compression. See _<<Cache
+    compression>>_ for more information. This can potentially take a long time
+    since all files in the cache need to be visited. Only files that are
+    currently compressed with a different level than _LEVEL_ will be
+    recompressed.
+
+*--trim-recompress-threads* _THREADS_::
+
+    Recompress using up to _THREADS_ threads with `--trim-recompress`. The
+    default is to use one thread per CPU.
+
+
 === Options for scripting or debugging
 
 *--checksum-file* _PATH_::
@@ -288,11 +316,16 @@ system-level configuration file and secondly a cache-specific configuration
 file. The priorities of configuration options are as follows (where 1 is
 highest):
 
-1. Environment variables.
-2. The cache-specific configuration file (see below).
-3. The system (read-only) configuration file `<sysconfdir>/ccache.conf`
+1. Command line settings in _KEY_=_VALUE_ form. Example:
++
+-------------------------------------------------------------------------------
+ccache debug=true compiler_check="%compiler% --version" gcc -c example.c
+-------------------------------------------------------------------------------
+2. Environment variables.
+3. The cache-specific configuration file (see below).
+4. The system (read-only) configuration file `<sysconfdir>/ccache.conf`
    (typically `/etc/ccache.conf` or `/usr/local/etc/ccache.conf`).
-4. Compile-time defaults.
+5. Compile-time defaults.
 
 As a special case, if the environment variable `CCACHE_CONFIGPATH` is set it
 specifies the configuration file, and the system configuration file won't be
@@ -655,6 +688,9 @@ _<<Cache debugging>>_.
 
     When true, ccache will just call the real compiler, bypassing the cache
     completely. The default is false.
++
+It is also possible to disable ccache for a specific source code file by adding
+the string `ccache:disable` in a comment in the first 4096 bytes of the file.
 
 [#config_extra_files_to_hash]
 *extra_files_to_hash* (*CCACHE_EXTRAFILES*)::
@@ -698,7 +734,7 @@ WARNING: Do not enable this option unless you are aware of these caveats:
   safety measure. Furthermore, a simple integrity check is made for cached
   object files by verifying that their sizes are correct. This means that
   mistakes like `strip file.o` or `echo >file.o` will be detected even if the
-  object file is made writeable, but a modification that doesn't change the file
+  object file is made writable, but a modification that doesn't change the file
   size will not.
 * Programs that don't expect that files from two different identical
   compilations are hard links to each other can fail.
@@ -756,7 +792,7 @@ might be incorrect.
 
     If true, ccache will cache source file hashes based on device, inode and
     timestamps. This reduces the time spent on hashing include files since the
-    result can be resused between compilations. The default is true. The feature
+    result can be reused between compilations. The default is true. The feature
     requires <<config_temporary_dir,*temporary_dir*>> to be located on a local
     filesystem of a supported type.
 +
@@ -769,13 +805,6 @@ NOTE: The inode cache feature is currently not available on Windows.
     output. The default is false. This can be used to check documentation with
     `-Wdocumentation`.
 
-[#config_limit_multiple]
-*limit_multiple* (*CCACHE_LIMIT_MULTIPLE*)::
-
-    Sets the limit when cleaning up. Files are deleted (in LRU order) until the
-    levels are below the limit. The default is 0.8 (= 80%). See
-    _<<Automatic cleanup>>_ for more information.
-
 [#config_log_file]
 *log_file* (*CCACHE_LOGFILE*)::
 
@@ -900,13 +929,15 @@ temporary files otherwise. You may also want to set <<config_stats,*stats*>> to
 
     This option specifies one or several storage backends (separated by space)
     to query after checking the local cache (unless
-    <<config_remote_only,*remote_only*>> is true). See _<<Remote storage
-    backends>>_ for documentation of syntax and available backends.
+    <<config_remote_only,*remote_only*>> is true). See
+    _<<Remote storage backends>>_ for documentation of syntax and available
+    backends.
 +
 Examples:
 +
 * `+file:/shared/nfs/directory+`
 * `+file:///shared/nfs/one|read-only file:///shared/nfs/two+`
+* `+file:///Z:/example/windows/folder+`
 * `+http://example.com/cache+`
 * `+redis://example.com+`
 +
@@ -1020,8 +1051,8 @@ See the discussion under _<<Troubleshooting>>_ for more information.
 [#config_stats]
 *stats* (*CCACHE_STATS* or *CCACHE_NOSTATS*, see _<<Boolean values>>_ above)::
 
-    If true, ccache will update the statistics counters on each compilation.
-    The default is true.
+    If true, ccache will update the statistics counters on each compilation. The
+    default is true. If false, _<<automatic cleanup>>_ will be disabled as well.
 
 [#config_stats_log]
 *stats_log* (*CCACHE_STATSLOG*)::
@@ -1051,6 +1082,15 @@ filesystem as the `CCACHE_DIR` path, but this requirement has been relaxed.
     cache with other users.
 
 
+=== Disabling ccache
+
+To disable ccache completely for all invocations, set <<config_disable,*disable
+= true*>> (`CCACHE_DISABLE=1`). You can also disable ccache for a certain source
+code file by adding the string `ccache:disable` in a comment in the first 4096
+bytes of the file. In the latter case the `Ccache disabled` statistics counter
+will be increased.
+
+
 == Remote storage backends
 
 The <<config_remote_storage,*remote_storage*>> option lets you configure ccache
@@ -1146,7 +1186,8 @@ Examples:
 
 * `+file:/shared/nfs/directory+`
 * `+file:///shared/nfs/directory|umask=002|update-mtime=true+`
-* `+file://example.com/shared/folder+`
+* `+file:///Z:/example/windows/folder+`
+* `+file://example.com/shared/ccache%20folder+`
 
 Optional attributes:
 
@@ -1260,45 +1301,20 @@ Cleanup can be triggered in two different ways: automatic and manual.
 
 === Automatic cleanup
 
-Ccache maintains counters for various statistics about the cache, including the
-size and number of all cached files. In order to improve performance and reduce
-issues with concurrent ccache invocations, there is one statistics file for
-each of the sixteen subdirectories in the cache.
-
-After a new compilation result has been written to the cache, ccache will
-update the size and file number statistics for the subdirectory (one of
-sixteen) to which the result was written. Then, if the size counter for said
-subdirectory is greater than *max_size / 16* or the file number counter is
-greater than *max_files / 16*, automatic cleanup is triggered.
-
-When automatic cleanup is triggered for a subdirectory in the cache, ccache
-will:
-
-1. Count all files in the subdirectory and compute their aggregated size.
-2. Remove files in LRU (least recently used) order until the size is at most
-   *limit_multiple * max_size / 16* and the number of files is at most
-   *limit_multiple * max_files / 16*, where
-   <<config_limit_multiple,*limit_multiple*>>, <<config_max_size,*max_size*>>
-   and <<config_max_files,*max_files*>> are configuration options.
-3. Set the size and file number counters to match the files that were kept.
-
-The reason for removing more files than just those needed to not exceed the max
-limits is that a cleanup is a fairly slow operation, so it would not be a good
-idea to trigger it often, like after each cache miss.
-
-The LRU cleanup makes use of the file modification time (mtime) of cache
-entries; ccache updates mtime of the cache entries read on a cache hit to mark
-them as "recently used".
+After a new compilation result has been written to the local cache, ccache will
+trigger an automatic cleanup if <<config_max_size,*max_size*>> or
+<<config_max_files,*max_files*>> is exceeded. The cleanup removes cache entries
+in LRU (least recently used) order based on the modification time (mtime) of
+files in the cache. For this reason, ccache updates mtime of the cache files
+read on a cache hit to mark them as recently used.
 
 
 === Manual cleanup
 
-You can run `ccache -c/--cleanup` to force cleanup of the whole cache, i.e. all
-of the sixteen subdirectories. This will recalculate the statistics counters
-and make sure that the configuration options *max_size* and
-<<config_max_files,*max_files*>> are not exceeded. Note that
-<<config_limit_multiple,*limit_multiple*>> is not taken into account for manual
-cleanup.
+You can run `ccache -c/--cleanup` to force cleanup of the whole cache. This will
+recalculate the cache size information and also make sure that the cache size
+does not exceed <<config_max_size,*max_size*>> and
+<<config_max_files,*max_files*>>.
 
 
 == Cache compression
@@ -1378,6 +1394,9 @@ produce a single object file from a single source file.
 | Called for preprocessing |
 The compiler was called for preprocessing, not compiling.
 
+| Ccache disabled |
+Ccache was disabled by a `ccache:disable` string in the source code file.
+
 | Could not use modules |
 Preconditions for using <<C++ modules>> were not fulfilled.
 
@@ -1546,8 +1565,8 @@ The direct mode will be disabled if any of the following holds:
 * a modification time of one of the include files is too new (needed to avoid a
   race condition)
 * a compiler option not supported by the direct mode is used:
-** a `-Wp,++*++` compiler option other than `-Wp,-MD,<path>`, `-Wp,-MMD,<path>`
-   and `-Wp,-D<define>`
+** a `-Wp,++*++` compiler option other than `-Wp,-MD,<path>`, `-Wp,-MMD,<path>`,
+   `-Wp,-D<macro[=defn]>` or `-Wp,-U<macro>`
 ** `-Xpreprocessor`
 * the string `+__TIME__+` is present in the source code
 
@@ -1573,7 +1592,7 @@ Disadvantages:
   or source code will make the hash different. Compare this with the default
   setup where ccache will fall back to the preprocessor mode, which is tolerant
   to some types of changes of compiler options and source code changes.
-* If -MD is used, the manifest entries will include system header files as
+* If `-MD` is used, the manifest entries will include system header files as
   well, thus slowing down cache hits slightly, just as using `-MD` slows down
   make. This is also the case for MSVC with `/showIncludes`.
 * If `-MMD` is used, the manifest entries will not include system header files,
index 7249904..e09c7ca 100644 (file)
@@ -1,5 +1,275 @@
 = Ccache news
 
+== Ccache 4.8.3
+
+Release date: 2023-08-29
+
+=== Bug fixes
+
+- Fixed various problems with parsing of MSVC response file (`.rsp`). +
+  [small]#_[contributed by Jiri Hörner]_#
+
+- Fixed handling of NVCC `-Xcompiler` and `--Werror` options. +
+  [small]#_[contributed by Andrew Hardin]_#
+
+- Fixed bookkeeping of files when hard linking or file cloning is enabled. In
+  ccache 4.8–4.8.2 this could result in incorrect size/count statistics after
+  automatic or explicit cleanup. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Made a workaround for GCC 12.3 bug 109241 where GCC fails to compile ccache. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Upgraded to xxHash 0.8.2, which fixes compilation of ccache with GCC 12 and
+  `-Og`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.8.2
+
+Release date: 2023-06-12
+
+
+=== Bug fixes
+
+- Fixed parsing of Windows drive letter in file URLs for remote storage. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed a bug affecting depend mode with MSVC. +
+  [small]#_[contributed by Huang Qin Jin]_#
+
+- Ccache no longer passes `-v` to the preprocessor. This improves preprocessor
+  mode hit rate when `-v` is on the compiler command line. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Made `--trim-max-size` accept 0 for no limit. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Made various fixes for Windows 64-bit MSBuild builds. +
+  [small]#_[contributed by Rafael Kitover]_#
+
+- Silenced CMake warning for extracted timestamps. +
+  [small]#_[contributed by Rafael Kitover]_#
+
+- Worked around problem with building ZStandard with Xcode. +
+  [small]#_[contributed by Gregor Jasny]_#
+
+- Fixed undefined behavior (only triggered by unit tests) in
+  `util::read_file_part` for zero count, making the build fail with
+  `+_GLIBCXX_ASSERTIONS+`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Clarified `--evict-older-than` semantics. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Test improvements
+
+- Fixed typo in "`Directory is not hashed if using -gz`" test. +
+  [small]#_[contributed by Sam James]_#
+
+
+== Ccache 4.8.1
+
+Release date: 2023-05-19
+
+
+=== Bug fixes
+
+- Fixed an issue with the depend mode in combination with `--` on the command
+  line for Clang-based compilers. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved knowledge about MSVC debug flags so that non-debug `/Z*` options are
+  once again supported. +
+  [small]#_[contributed by Stephan Rohmen]_#
+
+- Ccache no longer treats `/Zi` as unsupported for clang-cl. +
+  [small]#_[contributed by Tobias Hieta]_#
+
+- Made the output format of `ccache -k max_size` parsable by ccache itself. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build/CI improvements
+
+- Corrected ccache version in the macOS binary release. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Made it possible to build ccache with clang-cl on Windows. +
+  [small]#_[contributed by Tobias Hieta]_#
+
+- Upgraded to doctest 2.4.11, thereby fixing a build issue on Solaris. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Added a remote file storage example with URL-encoded spaces. +
+  [small]#_[contributed by Joel Rosdahl]_
+
+
+== Ccache 4.8
+
+Release date: 2023-03-12
+
+
+=== New features and improvements
+
+- Improved the automatic cache cleanup mechanism. Automatic cleanups are now
+  performed on 1/256 of the cache instead of 1/16, thus making them much quicker
+  (but naturally more frequent). Cleanups are coordinated between ccache
+  processes so that at most one process will perform cleanup at a time. Also,
+  the actual cache size will stay very close to the configured maximum size
+  instead of staying around 90% as was the case before. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for setting per-compilation configuration options on the command
+  line. Example: `ccache hash_dir=false gcc -c example.c`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Made it possible to disable ccache for a certain source code file by embedding
+  the string `ccache:disable` in a comment near the top of the file. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Made ccache understand that an MSVC `/Z7` option overrides an earlier `/Z*`
+  option and thus is not too hard to cache. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a `--recompress-threads` command line option for selecting the number of
+  CPU threads to use when recompressing the local cache. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added `--trim-recompress` and `--trim-recompress-threads` command line options
+  for recompressing file-based remote storage. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added tmpfs, ufs and zfs to the list of supported filesystems on macOS and
+  BSDs for the inode cache. +
+  [small]#_[contributed by Oleg Sidorkin]_#
+
+- Improved progress bars for clean/clear/evict-style operations. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved printing of cache sizes in various outputs. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Activate debug logging for command mode options like `--cleanup`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for `-Wp,-U<macro>` in the direct mode. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added quotes around arguments with space in logged command lines. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added logging of executed command lines on Windows. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Made sure not to update the stats file when there are no incremented
+  counters. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved actual disk size calculation on Windows. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build/CI improvements
+
+- Added CI support for building macOS universal binaries. +
+  [small]#_[contributed by Raihaan Shouhell]_#
+
+- Make it possible to force download of Zstd and Hiredis, e.g. with `cmake -D
+  ZSTD_FROM_INTERNET=ON [...]`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- Fixed an edge case where a non-temporal identifier is misidentified. +
+  [small]#_[contributed by Erik Flodin]_#
+
+- Fixed reporting of local/remote cache misses in depend mode. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed parsing of backslashes in MSVC RSP files. +
+  [small]#_[contributed by Raihaan Shouhell]_#
+
+- Fixed a crash in `--show-log-stats` when the stats log file doesn't exist. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed matching of base directory for MSVC. The base directory will now match
+  case-insensitively with absolute paths in preprocessed output, or from
+  `/showIncludes` in the depend mode case, when compiling with MSVC. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed a problem where the original umask would be used when storing a remote
+  cache result in the local cache. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Changed the inode cache implementation to use spinlocks instead of pthread
+  mutexes. This makes the inode cache work on FreeBSD and similar systems. +
+  [small]#_[contributed by Oleg Sidorkin]_#
+
+- Don't treat `-Wp,-D` as interchangeable with `-D`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Disable the inode cache if the filesystem risks getting full soon. This fixes
+  a problem when the cache is on a filesystem where `posix_fallocate` isn't
+  reliable, like Btrfs with compression enabled. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed performance of cache path relativization in preprocessed output,
+  primarily on Windows where `stat` calls are relatively costly. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed rare crash in the signal handler at process exit. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed handling of Unix-style paths passed to MSVC. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed so that the config options and command line are logged before trying to
+  locate the compiler and exiting early. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Improved description of `--set-config`. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed broken markup in the manual. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a note to the manual that `stats = false` will disable automatic
+  cleanup. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+- Fix a bad reference to the "`Remote storage backends`" section. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.7.5
+
+Release date: 2023-03-20
+
+=== Bug fixes
+
+- Disabled the inode cache by default again since there have reports of ccache
+  processes hanging on futex calls related to the inode cache. +
+  [small]#_[contributed by Joel Rosdahl]_#
+
+
 == Ccache 4.7.4
 
 Release date: 2022-11-21
@@ -2245,7 +2515,7 @@ Release date: 2018-02-11
 - ccache now treats an unreadable configuration file just like a missing
   configuration file.
 
-- Documented more pitfalls with enabling `hard_links` (`CCACHE_HARDLINK`).
+- Documented more pitfalls with enabling `hard_link` (`CCACHE_HARDLINK`).
 
 - Documented caveats related to colored warnings from compilers.
 
index b198be2..a94422c 100755 (executable)
@@ -17,22 +17,23 @@ fi
 
 top_dir="$(dirname "$0")"
 clang_format_exe="$top_dir/.clang-format-exe"
-clang_format_version=10
-url_prefix="https://github.com/muttleyxd/clang-tools-static-binaries/releases/download/master-22538c65/clang-format-${clang_format_version}_"
+clang_format_version=11
+clang_format_release=master-1d7ec53d
+url_prefix="https://github.com/muttleyxd/clang-tools-static-binaries/releases/download/${clang_format_release}/clang-format-${clang_format_version}_"
 
 if [ ! -x "$clang_format_exe" ]; then
     case "$(uname -s | tr '[:upper:]' '[:lower:]')" in
         *mingw*|*cygwin*|*msys*)
             url_suffix=windows-amd64.exe
-            checksum=0b21dfb9041437eebcc44032096e4dfba9ee7b668ee6867ada71ea3541f7b09657ea592a5b1cf659bc9f8072bdc477dfc9da07b3edacb7834b70e68d7a3fc887
+            checksum=7167f201acbd8ff06a7327d14db0d8a169393384bbc42873bb8277b788cc4469
             ;;
         *darwin*)
             url_suffix=macosx-amd64
-            checksum=8458753e13d3cbb7949a302beab812bed6d9dd9001c6e9618e5ba2e438233633ae04704a4e150aa2abfbaf103f1df4bc4a77b306012f44b37e543964bd527951
+            checksum=0cab857e66aa9ed9ea00eee3c94bfc91a2d72ab440327e15784852cf94b1814b
             ;;
         *linux*)
             url_suffix=linux-amd64
-            checksum=3c4aaa90ad83313a1d7b350b30f9ad62578be73241f00f7d6e92838289e0b734fab80dee9dfcbd1546135bdb4e3601cfb2cf6b47360fba0bfc961e5a7cab2015
+            checksum=a9d76e3275823ea308bc2e4d2a6e3cc693f38c29d891f1d74cb0e1553e698dea
             ;;
         *)
             echo "Error: Please set CLANG_FORMAT to clang-format version $clang_format_version" >&2
@@ -51,9 +52,9 @@ if [ ! -x "$clang_format_exe" ]; then
         exit 1
     fi
 
-    if ! command -v sha512sum >/dev/null; then
-        echo "Warning: sha512sum not found, not verifying clang-format integrity" >&2
-    elif ! echo "$checksum $clang_format_exe.tmp" | sha512sum --status -c; then
+    if ! command -v sha256sum >/dev/null; then
+        echo "Warning: sha256sum not found, not verifying clang-format integrity" >&2
+    elif ! echo "$checksum $clang_format_exe.tmp" | sha256sum --status -c; then
         echo "Error: Bad checksum of downloaded clang-format" >&2
         exit 1
     fi
index 0352b47..1bbebd7 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 
-# This script downloads the contents of the local cache from a Redis remote
-# storage.
+# This script downloads the contents of the local cache
+# from a Redis remote storage.
 
 import redis
 import os
@@ -53,10 +53,10 @@ for key in context.scan_iter():
         continue
     if val[0:2] == b"\xcc\xac":  # magic
         objects += 1
-        if val[2] == 0 and val[3] == 0:
+        if val[3] == 0:
             ext = "R"
             result += 1
-        elif val[2] == 0 and val[3] == 1:
+        elif val[3] == 1:
             ext = "M"
             manifest += 1
         else:
diff --git a/misc/typos.toml b/misc/typos.toml
new file mode 100644 (file)
index 0000000..f1eb931
--- /dev/null
@@ -0,0 +1,9 @@
+[default.extend-words]
+fo = "fo"
+hda = "hda"
+ot = "ot"
+seve = "seve"
+thi = "thi"
+
+[files]
+extend-exclude = ["misc/codespell-allowlist.txt", "src/third_party"]
index b3595c5..a1f9cb0 100755 (executable)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 
-# This script uploads the contents of the local cache to a Redis remote storage.
+# This script uploads the contents of the local cache
+# to a Redis remote storage.
 
 import redis
 import os
@@ -16,20 +17,24 @@ elif config.startswith("/"):
     host, port, sock = None, None, config
 else:
     host, port, sock = config, 6379, None
+
 username = os.getenv("REDIS_USERNAME")
 password = os.getenv("REDIS_PASSWORD")
 context = redis.Redis(host=host, port=port, unix_socket_path=sock, password=password)
 pipe = context.pipeline(transaction=False)
 
+use_setnx = True
+
 ccache = os.getenv("CCACHE_DIR", os.path.expanduser("~/.cache/ccache"))
 filelist = []
-for dirpath, dirnames, filenames in os.walk(ccache):
+for dirpath, dirnames, filenames in os.walk(ccache, topdown=True):
     # sort by modification time, most recently used last
     for filename in filenames:
         if filename.endswith(".lock"):
             continue
         stat = os.stat(os.path.join(dirpath, filename))
         filelist.append((stat.st_mtime, dirpath, filename, stat.st_size))
+    dirnames[:] = [d for d in dirnames if d != "tmp"]
 filelist.sort()
 
 files = result = manifest = objects = 0
@@ -42,12 +47,7 @@ bar = progress.bar.Bar(
 )
 for mtime, dirpath, filename, filesize in filelist:
     dirname = dirpath.replace(ccache + os.path.sep, "")
-    if dirname == "tmp":
-        continue
-    elif filename == "CACHEDIR.TAG" or filename == "stats":
-        # ignore these
-        files += 1
-    else:
+    if filename != "ccache.conf" and filename != "CACHEDIR.TAG" and filename != "stats":
         (base, ext) = filename[:-1], filename[-1:]
         if ext == "R" or ext == "M":
             if ext == "R":
@@ -58,7 +58,10 @@ for mtime, dirpath, filename, filesize in filelist:
             val = open(os.path.join(dirpath, filename), "rb").read() or None
             if val:
                 # print("%s: %s %d" % (key, ext, len(val)))
-                pipe.setnx(key, val)
+                if use_setnx:
+                    pipe.setnx(key, val)
+                else:
+                    pipe.set(key, val)
                 objects += 1
         files += 1
         size += filesize
index 48c5e0a..205926d 100644 (file)
@@ -8,59 +8,65 @@
 # them.
 
 ---
-Checks:          '-*,
-                  readability-*,
-                  -readability-implicit-bool-conversion,
-                  -readability-magic-numbers,
-                  -readability-else-after-return,
-                  -readability-function-cognitive-complexity,
-                  -readability-named-parameter,
-                  -readability-qualified-auto,
-                  -readability-redundant-declaration,
-                  performance-*,
-                  -performance-unnecessary-value-param,
-                  modernize-*,
-                  -modernize-avoid-c-arrays,
-                  -modernize-pass-by-value,
-                  -modernize-return-braced-init-list,
-                  -modernize-use-auto,
-                  -modernize-use-default-member-init,
-                  -modernize-use-nodiscard,
-                  -modernize-use-trailing-return-type,
-                  cppcoreguidelines-*,
-                  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-                  -cppcoreguidelines-pro-type-vararg,
-                  -cppcoreguidelines-owning-memory,
-                  -cppcoreguidelines-avoid-magic-numbers,
-                  -cppcoreguidelines-avoid-non-const-global-variables,
-                  -cppcoreguidelines-const-correctness,
-                  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
-                  -cppcoreguidelines-no-malloc,
-                  -cppcoreguidelines-init-variables,
-                  -cppcoreguidelines-avoid-c-arrays,
-                  -cppcoreguidelines-pro-bounds-constant-array-index,
-                  -cppcoreguidelines-pro-type-member-init,
-                  -cppcoreguidelines-macro-usage,
-                  -cppcoreguidelines-pro-type-const-cast,
-                  -cppcoreguidelines-pro-type-reinterpret-cast,
-                  -cppcoreguidelines-pro-type-union-access,
-                  -cppcoreguidelines-narrowing-conversions,
-                  -cppcoreguidelines-non-private-member-variables-in-classes,
-                  -cppcoreguidelines-special-member-functions,
-                  bugprone-*,
-                  -bugprone-signed-char-misuse,
-                  -bugprone-branch-clone,
-                  -bugprone-narrowing-conversions,
-                  cert-*,
-                  -cert-err34-c,
-                  -cert-dcl50-cpp,
-                  -cert-dcl58-cpp,
-                  -cert-err58-cpp,
-                  clang-diagnostic-*,
-                  clang-analyzer-*,
-                  -clang-analyzer-alpha*,
-                  -clang-analyzer-valist.Uninitialized,
-                  -clang-analyzer-optin.performance.Padding'
+Checks: '
+  -*,
+  bugprone-*,
+  -bugprone-branch-clone,
+  -bugprone-easily-swappable-parameters,-warnings-as-errors,
+  -bugprone-implicit-widening-of-multiplication-result,
+  -bugprone-narrowing-conversions,
+  -bugprone-signed-char-misuse,
+  -bugprone-unhandled-exception-at-new,
+  cert-*,
+  -cert-dcl50-cpp,
+  -cert-dcl58-cpp,
+  -cert-err34-c,
+  -cert-err58-cpp,
+  clang-analyzer-*,
+  -clang-analyzer-alpha*,
+  -clang-analyzer-optin.performance.Padding,
+  -clang-analyzer-valist.Uninitialized,
+  clang-diagnostic-*,
+  cppcoreguidelines-*,
+  -cppcoreguidelines-avoid-c-arrays,
+  -cppcoreguidelines-avoid-magic-numbers,
+  -cppcoreguidelines-avoid-non-const-global-variables,
+  -cppcoreguidelines-const-correctness,
+  -cppcoreguidelines-init-variables,
+  -cppcoreguidelines-macro-usage,
+  -cppcoreguidelines-narrowing-conversions,
+  -cppcoreguidelines-no-malloc,
+  -cppcoreguidelines-non-private-member-variables-in-classes,
+  -cppcoreguidelines-owning-memory,
+  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+  -cppcoreguidelines-pro-bounds-constant-array-index,
+  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+  -cppcoreguidelines-pro-type-const-cast,
+  -cppcoreguidelines-pro-type-member-init,
+  -cppcoreguidelines-pro-type-reinterpret-cast,
+  -cppcoreguidelines-pro-type-union-access,
+  -cppcoreguidelines-pro-type-vararg,
+  -cppcoreguidelines-special-member-functions,
+  modernize-*,
+  -modernize-avoid-c-arrays,
+  -modernize-pass-by-value,
+  -modernize-return-braced-init-list,
+  -modernize-use-auto,
+  -modernize-use-default-member-init,
+  -modernize-use-nodiscard,
+  -modernize-use-trailing-return-type,
+  performance-*,
+  -performance-unnecessary-value-param,
+  readability-*,
+  -readability-convert-member-functions-to-static,
+  -readability-else-after-return,
+  -readability-function-cognitive-complexity,
+  -readability-implicit-bool-conversion,
+  -readability-magic-numbers,
+  -readability-named-parameter,
+  -readability-qualified-auto,
+  -readability-redundant-declaration,
+  '
 WarningsAsErrors: '*'
 # Only include headers directly in src.
 HeaderFilterRegex: 'src/[^/]*$'
index 50cfa67..2b05673 100644 (file)
@@ -60,7 +60,7 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
   auto pos = argtext->c_str();
   std::string argbuf;
   argbuf.resize(argtext->length() + 1);
-  auto argpos = argbuf.begin();
+  auto argpos = argbuf.data();
 
   // Used to track quoting state; if \0 we are not inside quotes. Otherwise
   // stores the quoting character that started it for matching the end quote.
@@ -69,17 +69,40 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
   while (true) {
     switch (*pos) {
     case '\\':
-      pos++;
       switch (format) {
       case AtFileFormat::gcc:
+        pos++;
         if (*pos == '\0') {
           continue;
         }
         break;
       case AtFileFormat::msvc:
-        if (*pos != '"' && *pos != '\\') {
-          pos--;
+        size_t count = 0;
+        while (*pos == '\\') {
+          count++;
+          pos++;
         }
+        if (*pos == '"') {
+          if (count == 1) {
+            // simple escape \"
+            break;
+          }
+          if (count % 2 != 0) {
+            // If an odd number of backslashes is followed by a double quotation
+            // mark, one backslash is placed in the argv array for every pair of
+            // backslashes, and the double quotation mark is "escaped" by the
+            // remaining backslash
+            pos--;
+          }
+          std::memset(argpos, '\\', count / 2);
+          argpos += count / 2;
+        } else {
+          // Backslashes are interpreted literally, unless they immediately
+          // precede a double quotation mark.
+          std::memset(argpos, '\\', count);
+          argpos += count;
+        }
+        continue;
         break;
       }
       break;
@@ -95,6 +118,13 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
         if (quoting == *pos) {
           quoting = '\0';
           pos++;
+          if (format == AtFileFormat::msvc && *pos == '"') {
+            // Any double-quote directly following a closing quote is treated as
+            // (or as part of) plain unwrapped text that is adjacent to the
+            // double-quoted group
+            // https://stackoverflow.com/questions/7760545/escape-double-quotes-in-parameter
+            break;
+          }
           continue;
         } else {
           break;
@@ -120,7 +150,7 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
       if (argbuf[0] != '\0') {
         args.push_back(argbuf.substr(0, argbuf.find('\0')));
       }
-      argpos = argbuf.begin();
+      argpos = argbuf.data();
       if (*pos == '\0') {
         return args;
       } else {
index c9d82c3..9c0f0cb 100644 (file)
@@ -60,6 +60,15 @@ AtomicFile::write(nonstd::span<const uint8_t> data)
 }
 
 void
+AtomicFile::flush()
+{
+  if (fflush(m_stream) != 0) {
+    throw core::Error(
+      FMT("failed to flush data to {}: {}", m_path, strerror(errno)));
+  }
+}
+
+void
 AtomicFile::commit()
 {
   ASSERT(m_stream);
index 37ac372..033a3fa 100644 (file)
@@ -38,6 +38,7 @@ public:
 
   void write(std::string_view data);
   void write(nonstd::span<const uint8_t> data);
+  void flush();
 
   // Close the temporary file and rename it to the destination file. Note: The
   // destructor will not do this automatically to avoid half-written data in the
index 79f0fd1..55cfaa4 100644 (file)
@@ -9,7 +9,6 @@ set(
   Hash.cpp
   Logging.cpp
   ProgressBar.cpp
-  SignalHandler.cpp
   Stat.cpp
   TemporaryFile.cpp
   ThreadPool.cpp
@@ -34,6 +33,8 @@ endif()
 
 if(WIN32)
   list(APPEND source_files Win32Util.cpp)
+else()
+  list(APPEND source_files SignalHandler.cpp)
 endif()
 
 file(GLOB headers *.hpp)
index 75a1358..982134e 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -80,7 +80,6 @@ enum class ConfigItem {
   ignore_options,
   inode_cache,
   keep_comments_cpp,
-  limit_multiple,
   log_file,
   max_files,
   max_size,
@@ -136,7 +135,6 @@ const std::unordered_map<std::string, ConfigKeyTableEntry> k_config_key_table =
     {"ignore_options", {ConfigItem::ignore_options}},
     {"inode_cache", {ConfigItem::inode_cache}},
     {"keep_comments_cpp", {ConfigItem::keep_comments_cpp}},
-    {"limit_multiple", {ConfigItem::limit_multiple}},
     {"log_file", {ConfigItem::log_file}},
     {"max_files", {ConfigItem::max_files}},
     {"max_size", {ConfigItem::max_size}},
@@ -186,7 +184,6 @@ const std::unordered_map<std::string, std::string> k_env_variable_table = {
   {"IGNOREHEADERS", "ignore_headers_in_manifest"},
   {"IGNOREOPTIONS", "ignore_options"},
   {"INODECACHE", "inode_cache"},
-  {"LIMIT_MULTIPLE", "limit_multiple"},
   {"LOGFILE", "log_file"},
   {"MAXFILES", "max_files"},
   {"MAXSIZE", "max_size"},
@@ -249,12 +246,6 @@ format_bool(bool value)
   return value ? "true" : "false";
 }
 
-std::string
-format_cache_size(uint64_t value)
-{
-  return Util::format_parsable_size_with_suffix(value);
-}
-
 CompilerType
 parse_compiler_type(const std::string& value)
 {
@@ -285,31 +276,31 @@ parse_sloppiness(const std::string& value)
 
   for (const auto token : util::Tokenizer(value, ", ")) {
     if (token == "clang_index_store") {
-      result.enable(core::Sloppy::clang_index_store);
+      result.insert(core::Sloppy::clang_index_store);
     } else if (token == "file_stat_matches") {
-      result.enable(core::Sloppy::file_stat_matches);
+      result.insert(core::Sloppy::file_stat_matches);
     } else if (token == "file_stat_matches_ctime") {
-      result.enable(core::Sloppy::file_stat_matches_ctime);
+      result.insert(core::Sloppy::file_stat_matches_ctime);
     } else if (token == "gcno_cwd") {
-      result.enable(core::Sloppy::gcno_cwd);
+      result.insert(core::Sloppy::gcno_cwd);
     } else if (token == "include_file_ctime") {
-      result.enable(core::Sloppy::include_file_ctime);
+      result.insert(core::Sloppy::include_file_ctime);
     } else if (token == "include_file_mtime") {
-      result.enable(core::Sloppy::include_file_mtime);
+      result.insert(core::Sloppy::include_file_mtime);
     } else if (token == "ivfsoverlay") {
-      result.enable(core::Sloppy::ivfsoverlay);
+      result.insert(core::Sloppy::ivfsoverlay);
     } else if (token == "locale") {
-      result.enable(core::Sloppy::locale);
+      result.insert(core::Sloppy::locale);
     } else if (token == "modules") {
-      result.enable(core::Sloppy::modules);
+      result.insert(core::Sloppy::modules);
     } else if (token == "pch_defines") {
-      result.enable(core::Sloppy::pch_defines);
+      result.insert(core::Sloppy::pch_defines);
     } else if (token == "random_seed") {
-      result.enable(core::Sloppy::random_seed);
+      result.insert(core::Sloppy::random_seed);
     } else if (token == "system_headers" || token == "no_system_headers") {
-      result.enable(core::Sloppy::system_headers);
+      result.insert(core::Sloppy::system_headers);
     } else if (token == "time_macros") {
-      result.enable(core::Sloppy::time_macros);
+      result.insert(core::Sloppy::time_macros);
     } // else: ignore unknown value for forward compatibility
   }
 
@@ -320,43 +311,43 @@ std::string
 format_sloppiness(core::Sloppiness sloppiness)
 {
   std::string result;
-  if (sloppiness.is_enabled(core::Sloppy::clang_index_store)) {
+  if (sloppiness.contains(core::Sloppy::clang_index_store)) {
     result += "clang_index_store, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::file_stat_matches)) {
+  if (sloppiness.contains(core::Sloppy::file_stat_matches)) {
     result += "file_stat_matches, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::file_stat_matches_ctime)) {
+  if (sloppiness.contains(core::Sloppy::file_stat_matches_ctime)) {
     result += "file_stat_matches_ctime, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::gcno_cwd)) {
+  if (sloppiness.contains(core::Sloppy::gcno_cwd)) {
     result += "gcno_cwd, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::include_file_ctime)) {
+  if (sloppiness.contains(core::Sloppy::include_file_ctime)) {
     result += "include_file_ctime, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::include_file_mtime)) {
+  if (sloppiness.contains(core::Sloppy::include_file_mtime)) {
     result += "include_file_mtime, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::ivfsoverlay)) {
+  if (sloppiness.contains(core::Sloppy::ivfsoverlay)) {
     result += "ivfsoverlay, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::locale)) {
+  if (sloppiness.contains(core::Sloppy::locale)) {
     result += "locale, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::modules)) {
+  if (sloppiness.contains(core::Sloppy::modules)) {
     result += "modules, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::pch_defines)) {
+  if (sloppiness.contains(core::Sloppy::pch_defines)) {
     result += "pch_defines, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::random_seed)) {
+  if (sloppiness.contains(core::Sloppy::random_seed)) {
     result += "random_seed, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::system_headers)) {
+  if (sloppiness.contains(core::Sloppy::system_headers)) {
     result += "system_headers, ";
   }
-  if (sloppiness.is_enabled(core::Sloppy::time_macros)) {
+  if (sloppiness.contains(core::Sloppy::time_macros)) {
     result += "time_macros, ";
   }
   if (!result.empty()) {
@@ -442,6 +433,24 @@ parse_config_file(const std::string& path,
   return true;
 }
 
+std::unordered_map<std::string, std::string>
+create_cmdline_settings_map(const std::vector<std::string>& settings)
+{
+  std::unordered_map<std::string, std::string> result;
+  for (const auto& setting : settings) {
+    DEBUG_ASSERT(setting.find('=') != std::string::npos);
+    std::string key;
+    std::string value;
+    std::string error_message;
+    bool ok = parse_line(setting, &key, &value, &error_message);
+    ASSERT(ok);
+    if (!key.empty()) {
+      result.insert_or_assign(std::move(key), std::move(value));
+    }
+  }
+  return result;
+}
+
 } // namespace
 
 #ifndef _WIN32
@@ -492,8 +501,11 @@ compiler_type_to_string(CompilerType compiler_type)
 }
 
 void
-Config::read()
+Config::read(const std::vector<std::string>& cmdline_config_settings)
 {
+  auto cmdline_settings_map =
+    create_cmdline_settings_map(cmdline_config_settings);
+
   const std::string home_dir = Util::get_home_directory();
   const std::string legacy_ccache_dir = Util::make_path(home_dir, ".ccache");
   const bool legacy_ccache_dir_exists =
@@ -528,8 +540,12 @@ Config::read()
     MTR_END("config", "conf_read_system");
 
     const char* const env_ccache_dir = getenv("CCACHE_DIR");
+    auto cmdline_cache_dir = cmdline_settings_map.find("cache_dir");
+
     std::string config_dir;
-    if (env_ccache_dir && *env_ccache_dir) {
+    if (cmdline_cache_dir != cmdline_settings_map.end()) {
+      config_dir = cmdline_cache_dir->second;
+    } else if (env_ccache_dir && *env_ccache_dir) {
       config_dir = env_ccache_dir;
     } else if (!cache_dir().empty() && !env_ccache_dir) {
       config_dir = cache_dir();
@@ -575,6 +591,8 @@ Config::read()
   // (cache_dir is set above if CCACHE_DIR is set.)
   MTR_END("config", "conf_update_from_environment");
 
+  update_from_map(cmdline_settings_map);
+
   if (cache_dir().empty()) {
     if (legacy_ccache_dir_exists) {
       set_cache_dir(legacy_ccache_dir);
@@ -597,7 +615,8 @@ Config::read()
   // else: cache_dir was set explicitly via environment or via system config.
 
   // We have now determined config.cache_dir and populated the rest of config in
-  // prio order (1. environment, 2. cache-specific config, 3. system config).
+  // prio order (1. command line, 2. environment, 3. cache-specific config, 4.
+  // system config).
 }
 
 const std::string&
@@ -630,12 +649,25 @@ Config::update_from_file(const std::string& path)
   return parse_config_file(
     path, [&](const auto& /*line*/, const auto& key, const auto& value) {
       if (!key.empty()) {
-        this->set_item(key, value, std::nullopt, false, path);
+        set_item(key, value, std::nullopt, false, path);
       }
     });
 }
 
 void
+Config::update_from_map(const std::unordered_map<std::string, std::string>& map)
+{
+  for (const auto& [key, value] : map) {
+    try {
+      set_item(key, value, std::nullopt, false, "command line");
+    } catch (core::Error& e) {
+      throw core::Error(
+        FMT("when parsing command line config \"{}\": {}", key, e.what()));
+    }
+  }
+}
+
+void
 Config::update_from_environment()
 {
   for (char** env = environ; *env; ++env) {
@@ -747,17 +779,21 @@ Config::get_string_value(const std::string& key) const
   case ConfigItem::keep_comments_cpp:
     return format_bool(m_keep_comments_cpp);
 
-  case ConfigItem::limit_multiple:
-    return FMT("{:.1f}", m_limit_multiple);
-
   case ConfigItem::log_file:
     return m_log_file;
 
   case ConfigItem::max_files:
     return FMT("{}", m_max_files);
 
-  case ConfigItem::max_size:
-    return format_cache_size(m_max_size);
+  case ConfigItem::max_size: {
+    auto result =
+      util::format_human_readable_size(m_max_size, m_size_prefix_type);
+    if (util::ends_with(result, " bytes")) {
+      // Special case to make the output parsable by util::parse_size.
+      result.resize(result.size() - 6);
+    }
+    return result;
+  }
 
   case ConfigItem::msvc_dep_prefix:
     return m_msvc_dep_prefix;
@@ -992,11 +1028,6 @@ Config::set_item(const std::string& key,
     m_keep_comments_cpp = parse_bool(value, env_var_key, negate);
     break;
 
-  case ConfigItem::limit_multiple:
-    m_limit_multiple = std::clamp(
-      util::value_or_throw<core::Error>(util::parse_double(value)), 0.0, 1.0);
-    break;
-
   case ConfigItem::log_file:
     m_log_file = Util::expand_environment_variables(value);
     break;
@@ -1006,9 +1037,13 @@ Config::set_item(const std::string& key,
       util::parse_unsigned(value, std::nullopt, std::nullopt, "max_files"));
     break;
 
-  case ConfigItem::max_size:
-    m_max_size = Util::parse_size(value);
+  case ConfigItem::max_size: {
+    const auto [size, prefix_type] =
+      util::value_or_throw<core::Error>(util::parse_size(value));
+    m_max_size = size;
+    m_size_prefix_type = prefix_type;
     break;
+  }
 
   case ConfigItem::msvc_dep_prefix:
     m_msvc_dep_prefix = Util::expand_environment_variables(value);
index b75ff43..cc489cf 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -21,6 +21,7 @@
 #include "NonCopyable.hpp"
 
 #include <core/Sloppiness.hpp>
+#include <util/string.hpp>
 
 #include <sys/types.h>
 
@@ -30,6 +31,7 @@
 #include <optional>
 #include <string>
 #include <unordered_map>
+#include <vector>
 
 enum class CompilerType {
   auto_guess,
@@ -49,7 +51,7 @@ class Config : NonCopyable
 public:
   Config() = default;
 
-  void read();
+  void read(const std::vector<std::string>& cmdline_config_settings = {});
 
   bool absolute_paths_in_stderr() const;
   const std::string& base_dir() const;
@@ -73,7 +75,6 @@ public:
   const std::string& ignore_options() const;
   bool inode_cache() const;
   bool keep_comments_cpp() const;
-  double limit_multiple() const;
   const std::string& log_file() const;
   uint64_t max_files() const;
   uint64_t max_size() const;
@@ -102,6 +103,7 @@ public:
   // Return true for MSVC (cl.exe), clang-cl, and icl.
   bool is_compiler_group_msvc() const;
 
+  util::SizeUnitPrefixType size_unit_prefix_type() const;
   std::string default_temporary_dir() const;
 
   void set_base_dir(const std::string& value);
@@ -117,7 +119,6 @@ public:
   void set_ignore_options(const std::string& value);
   void set_inode_cache(bool value);
   void set_max_files(uint64_t value);
-  void set_max_size(uint64_t value);
   void set_msvc_dep_prefix(const std::string& value);
   void set_run_second_cpp(bool value);
   void set_temporary_dir(const std::string& value);
@@ -140,6 +141,11 @@ public:
   // invalid configuration values.
   bool update_from_file(const std::string& path);
 
+  // Set config values from a map with key-value pairs.
+  //
+  // Throws Error on invalid configuration values.
+  void update_from_map(const std::unordered_map<std::string, std::string>& map);
+
   // Set config values from environment variables.
   //
   // Throws Error on invalid configuration values.
@@ -183,10 +189,9 @@ private:
   std::string m_ignore_options;
   bool m_inode_cache = true;
   bool m_keep_comments_cpp = false;
-  double m_limit_multiple = 0.8;
   std::string m_log_file;
   uint64_t m_max_files = 0;
-  uint64_t m_max_size = 5ULL * 1000 * 1000 * 1000;
+  uint64_t m_max_size = 5ULL * 1024 * 1024 * 1024;
   std::string m_msvc_dep_prefix = "Note: including file:";
   std::string m_path;
   bool m_pch_external_checksum = false;
@@ -207,6 +212,8 @@ private:
   std::optional<mode_t> m_umask;
 
   bool m_temporary_dir_configured_explicitly = false;
+  util::SizeUnitPrefixType m_size_prefix_type =
+    util::SizeUnitPrefixType::binary;
 
   std::unordered_map<std::string /*key*/, std::string /*origin*/> m_origins;
 
@@ -364,12 +371,6 @@ Config::keep_comments_cpp() const
   return m_keep_comments_cpp;
 }
 
-inline double
-Config::limit_multiple() const
-{
-  return m_limit_multiple;
-}
-
 inline const std::string&
 Config::log_file() const
 {
@@ -496,6 +497,12 @@ Config::umask() const
   return m_umask;
 }
 
+inline util::SizeUnitPrefixType
+Config::size_unit_prefix_type() const
+{
+  return m_size_prefix_type;
+}
+
 inline void
 Config::set_base_dir(const std::string& value)
 {
@@ -578,12 +585,6 @@ Config::set_max_files(uint64_t value)
 }
 
 inline void
-Config::set_max_size(uint64_t value)
-{
-  m_max_size = value;
-}
-
-inline void
 Config::set_msvc_dep_prefix(const std::string& value)
 {
   m_msvc_dep_prefix = value;
index c46d368..0a45d9e 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -49,11 +49,12 @@ Context::Context()
 }
 
 void
-Context::initialize()
+Context::initialize(Args&& compiler_and_args,
+                    const std::vector<std::string>& cmdline_config_settings)
 {
-  config.read();
+  orig_args = std::move(compiler_and_args);
+  config.read(cmdline_config_settings);
   Logging::init(config);
-
   ignore_header_paths =
     util::split_path_list(config.ignore_headers_in_manifest());
   set_ignore_options(Util::split_into_strings(config.ignore_options(), " "));
index 883c02f..3eee4e5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -51,7 +51,8 @@ public:
 
   // Read configuration, initialize logging, etc. Typically not called from unit
   // tests.
-  void initialize();
+  void initialize(Args&& compiler_and_args,
+                  const std::vector<std::string>& cmdline_config_settings);
 
   ArgsInfo args_info;
   Config config;
index eea2c09..5ace570 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -46,10 +46,8 @@ private:
   bool m_owned = false;
 };
 
-inline File::File(FILE* const file)
+inline File::File(FILE* const file) : m_file(file), m_owned(false)
 {
-  m_file = file;
-  m_owned = false;
 }
 
 inline File::File(const std::string& path, const char* mode)
index 9342279..db75d58 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -20,7 +20,6 @@
 
 #include "Config.hpp"
 #include "Digest.hpp"
-#include "Fd.hpp"
 #include "Finalizer.hpp"
 #include "Hash.hpp"
 #include "Logging.hpp"
 #include "Util.hpp"
 #include "fmtmacros.hpp"
 
-#include <util/TimePoint.hpp>
-
 #include <fcntl.h>
 #include <libgen.h>
+#include <sched.h>
 #include <sys/mman.h>
 #include <unistd.h>
 
@@ -67,13 +65,24 @@ namespace {
 // Note: The key is hashed using the main hash algorithm, so the version number
 // does not need to be incremented if said algorithm is changed (except if the
 // digest size changes since that affects the entry format).
-const uint32_t k_version = 1;
+const uint32_t k_version = 2;
 
 // Note: Increment the version number if constants affecting storage size are
 // changed.
 const uint32_t k_num_buckets = 32 * 1024;
 const uint32_t k_num_entries = 4;
 
+// Maximum time the spin lock loop will try before giving up.
+const auto k_max_lock_duration = util::Duration(5);
+
+// The memory-mapped file may reside on a filesystem with compression. Memory
+// accesses to the file risk crashing if such a filesystem gets full, so stop
+// using the inode cache well before this happens.
+const uint64_t k_min_fs_mib_left = 100; // 100 MiB
+
+// How long a filesystem space check is valid before we make a new one.
+const util::Duration k_fs_space_check_valid_duration(1);
+
 static_assert(Digest::size() == 20,
               "Increment version number if size of digest is changed.");
 static_assert(std::is_trivially_copyable<Digest>::value,
@@ -119,7 +128,10 @@ fd_is_on_known_to_work_file_system(int fd)
       // Is a filesystem you know works with the inode cache missing in this
       // list? Please submit an issue or pull request to the ccache project.
       "apfs",
+      "tmpfs",
+      "ufs",
       "xfs",
+      "zfs",
     };
     if (std::find(known_to_work_filesystems.begin(),
                   known_to_work_filesystems.end(),
@@ -140,6 +152,46 @@ fd_is_on_known_to_work_file_system(int fd)
   return known_to_work;
 }
 
+bool
+spin_lock(std::atomic<pid_t>& owner_pid, const pid_t self_pid)
+{
+  pid_t prev_pid = 0;
+  pid_t lock_pid = 0;
+  bool reset_timer = false;
+  util::TimePoint lock_time;
+  while (true) {
+    for (int i = 0; i < 10000; ++i) {
+      lock_pid = owner_pid.load(std::memory_order_relaxed);
+      if (lock_pid == 0
+          && owner_pid.compare_exchange_weak(
+            lock_pid, self_pid, std::memory_order_acquire)) {
+        return true;
+      }
+
+      if (prev_pid != lock_pid) {
+        // Check for changing PID here so ABA locking is detected with better
+        // probability.
+        prev_pid = lock_pid;
+        reset_timer = true;
+      }
+      sched_yield();
+    }
+    // If everything is OK, we should never hit this.
+    if (reset_timer) {
+      lock_time = util::TimePoint::now();
+      reset_timer = false;
+    } else if (util::TimePoint::now() - lock_time > k_max_lock_duration) {
+      return false;
+    }
+  }
+}
+
+void
+spin_unlock(std::atomic<pid_t>& owner_pid)
+{
+  owner_pid.store(0, std::memory_order_release);
+}
+
 } // namespace
 
 struct InodeCache::Key
@@ -162,7 +214,7 @@ struct InodeCache::Entry
 
 struct InodeCache::Bucket
 {
-  pthread_mutex_t mt;
+  std::atomic<pid_t> owner_pid;
   Entry entries[k_num_entries];
 };
 
@@ -182,17 +234,21 @@ InodeCache::mmap_file(const std::string& inode_cache_file)
     munmap(m_sr, sizeof(SharedRegion));
     m_sr = nullptr;
   }
-  Fd fd(open(inode_cache_file.c_str(), O_RDWR));
-  if (!fd) {
+  m_fd = Fd(open(inode_cache_file.c_str(), O_RDWR));
+  if (!m_fd) {
     LOG("Failed to open inode cache {}: {}", inode_cache_file, strerror(errno));
     return false;
   }
-  if (!fd_is_on_known_to_work_file_system(*fd)) {
+  if (!fd_is_on_known_to_work_file_system(*m_fd)) {
     return false;
   }
-  SharedRegion* sr = reinterpret_cast<SharedRegion*>(mmap(
-    nullptr, sizeof(SharedRegion), PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0));
-  fd.close();
+  SharedRegion* sr =
+    reinterpret_cast<SharedRegion*>(mmap(nullptr,
+                                         sizeof(SharedRegion),
+                                         PROT_READ | PROT_WRITE,
+                                         MAP_SHARED,
+                                         *m_fd,
+                                         0));
   if (sr == MMAP_FAILED) {
     LOG("Failed to mmap {}: {}", inode_cache_file, strerror(errno));
     return false;
@@ -258,40 +314,25 @@ InodeCache::with_bucket(const Digest& key_digest,
   Util::big_endian_to_int(key_digest.bytes(), hash);
   const uint32_t index = hash % k_num_buckets;
   Bucket* bucket = &m_sr->buckets[index];
-  int err = pthread_mutex_lock(&bucket->mt);
-#ifdef HAVE_PTHREAD_MUTEX_ROBUST
-  if (err == EOWNERDEAD) {
-    if (m_config.debug()) {
-      ++m_sr->errors;
-    }
-    err = pthread_mutex_consistent(&bucket->mt);
-    if (err) {
-      LOG(
-        "Can't consolidate stale mutex at index {}: {}", index, strerror(err));
-      LOG_RAW("Consider removing the inode cache file if the problem persists");
+  bool acquired_lock = spin_lock(bucket->owner_pid, m_self_pid);
+  while (!acquired_lock) {
+    LOG("Dropping inode cache file because of stale mutex at index {}", index);
+    if (!drop() || !initialize()) {
       return false;
     }
-    LOG("Wiping bucket at index {} because of stale mutex", index);
-    memset(bucket->entries, 0, sizeof(Bucket::entries));
-  } else {
-#endif
-    if (err != 0) {
-      LOG("Failed to lock mutex at index {}: {}", index, strerror(err));
-      LOG_RAW("Consider removing the inode cache file if problem persists");
+    if (m_config.debug()) {
       ++m_sr->errors;
-      return false;
     }
-#ifdef HAVE_PTHREAD_MUTEX_ROBUST
+    bucket = &m_sr->buckets[index];
+    acquired_lock = spin_lock(bucket->owner_pid, m_self_pid);
   }
-#endif
-
   try {
     bucket_handler(bucket);
   } catch (...) {
-    pthread_mutex_unlock(&bucket->mt);
+    spin_unlock(bucket->owner_pid);
     throw;
   }
-  pthread_mutex_unlock(&bucket->mt);
+  spin_unlock(bucket->owner_pid);
   return true;
 }
 
@@ -308,7 +349,7 @@ InodeCache::create_new_file(const std::string& filename)
     return false;
   }
   int err = Util::fallocate(*tmp_file.fd, sizeof(SharedRegion));
-  if (err) {
+  if (err != 0) {
     LOG("Failed to allocate file space for inode cache: {}", strerror(err));
     return false;
   }
@@ -326,14 +367,9 @@ InodeCache::create_new_file(const std::string& filename)
 
   // Initialize new shared region.
   sr->version = k_version;
-  pthread_mutexattr_t mattr;
-  pthread_mutexattr_init(&mattr);
-  pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
-#ifdef HAVE_PTHREAD_MUTEX_ROBUST
-  pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
-#endif
   for (auto& bucket : sr->buckets) {
-    pthread_mutex_init(&bucket.mt, &mattr);
+    bucket.owner_pid = 0;
+    memset(bucket.entries, 0, sizeof(Bucket::entries));
   }
 
   munmap(sr, sizeof(SharedRegion));
@@ -360,6 +396,24 @@ InodeCache::initialize()
     return false;
   }
 
+  if (m_fd) {
+    auto now = util::TimePoint::now();
+    if (now > m_last_fs_space_check + k_fs_space_check_valid_duration) {
+      m_last_fs_space_check = now;
+
+      struct statfs buf;
+      if (fstatfs(*m_fd, &buf) != 0) {
+        LOG("fstatfs failed: {}", strerror(errno));
+        return false;
+      }
+      if (buf.f_bavail * 512 < k_min_fs_mib_left * 1024 * 1024) {
+        LOG("Filesystem has less than {} MiB free space, not using inode cache",
+            k_min_fs_mib_left);
+        return false;
+      }
+    }
+  }
+
   if (m_sr) {
     return true;
   }
@@ -389,7 +443,8 @@ InodeCache::InodeCache(const Config& config, util::Duration min_age)
     // CCACHE_DISABLE_INODE_CACHE_MIN_AGE is only for testing purposes; see
     // test/suites/inode_cache.bash.
     m_min_age(getenv("CCACHE_DISABLE_INODE_CACHE_MIN_AGE") ? util::Duration(0)
-                                                           : min_age)
+                                                           : min_age),
+    m_self_pid(getpid())
 {
 }
 
@@ -410,22 +465,19 @@ InodeCache::available(int fd)
   return fd_is_on_known_to_work_file_system(fd);
 }
 
-bool
-InodeCache::get(const std::string& path,
-                ContentType type,
-                Digest& file_digest,
-                int* return_value)
+std::optional<HashSourceCodeResult>
+InodeCache::get(const std::string& path, ContentType type, Digest& file_digest)
 {
   if (!initialize()) {
-    return false;
+    return std::nullopt;
   }
 
   Digest key_digest;
   if (!hash_inode(path, type, key_digest)) {
-    return false;
+    return std::nullopt;
   }
 
-  bool found = false;
+  std::optional<HashSourceCodeResult> result;
   const bool success = with_bucket(key_digest, [&](const auto bucket) {
     for (uint32_t i = 0; i < k_num_entries; ++i) {
       if (bucket->entries[i].key_digest == key_digest) {
@@ -436,34 +488,32 @@ InodeCache::get(const std::string& path,
         }
 
         file_digest = bucket->entries[0].file_digest;
-        if (return_value) {
-          *return_value = bucket->entries[0].return_value;
-        }
-        found = true;
+        result =
+          HashSourceCodeResult::from_bitmask(bucket->entries[0].return_value);
         break;
       }
     }
   });
   if (!success) {
-    return false;
+    return std::nullopt;
   }
 
   if (m_config.debug()) {
-    LOG("Inode cache {}: {}", found ? "hit" : "miss", path);
-    if (found) {
+    LOG("Inode cache {}: {}", result ? "hit" : "miss", path);
+    if (result) {
       ++m_sr->hits;
     } else {
       ++m_sr->misses;
     }
   }
-  return found;
+  return result;
 }
 
 bool
 InodeCache::put(const std::string& path,
                 ContentType type,
                 const Digest& file_digest,
-                int return_value)
+                HashSourceCodeResult return_value)
 {
   if (!initialize()) {
     return false;
@@ -481,7 +531,7 @@ InodeCache::put(const std::string& path,
 
     bucket->entries[0].key_digest = key_digest;
     bucket->entries[0].file_digest = file_digest;
-    bucket->entries[0].return_value = return_value;
+    bucket->entries[0].return_value = return_value.to_bitmask();
   });
 
   if (!success) {
@@ -498,7 +548,7 @@ bool
 InodeCache::drop()
 {
   std::string file = get_file();
-  if (unlink(file.c_str()) != 0) {
+  if (unlink(file.c_str()) != 0 && errno != ENOENT) {
     return false;
   }
   LOG("Dropped inode cache {}", file);
index e270ef1..98276a3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #pragma once
 
+#include <Fd.hpp>
+#include <hashutil.hpp>
 #include <util/Duration.hpp>
+#include <util/TimePoint.hpp>
 
 #include <cstdint>
 #include <functional>
+#include <optional>
 #include <string>
 
 class Config;
@@ -74,10 +78,8 @@ public:
   //
   // Returns true if saved values could be retrieved from the cache, false
   // otherwise.
-  bool get(const std::string& path,
-           ContentType type,
-           Digest& file_digest,
-           int* return_value = nullptr);
+  std::optional<HashSourceCodeResult>
+  get(const std::string& path, ContentType type, Digest& file_digest);
 
   // Put hash digest and return value from a successful call to do_hash_file()
   // in hashutil.cpp.
@@ -86,7 +88,7 @@ public:
   bool put(const std::string& path,
            ContentType type,
            const Digest& file_digest,
-           int return_value = 0);
+           HashSourceCodeResult return_value);
 
   // Unmaps the current cache and removes the mapped file from disk.
   //
@@ -130,6 +132,9 @@ private:
 
   const Config& m_config;
   util::Duration m_min_age;
+  Fd m_fd;
   struct SharedRegion* m_sr = nullptr;
   bool m_failed = false;
+  const pid_t m_self_pid;
+  util::TimePoint m_last_fs_space_check;
 };
index 99a37a0..f3f93f0 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -20,6 +20,7 @@
 
 #include "fmtmacros.hpp"
 
+#include <assertions.hpp>
 #include <core/wincompat.hpp>
 
 #include "third_party/fmt/core.h"
@@ -74,25 +75,30 @@ ProgressBar::update(double value)
     return;
   }
 
+  DEBUG_ASSERT(value >= 0.0);
+  DEBUG_ASSERT(value <= 1.0);
+
   int16_t new_value = static_cast<int16_t>(1000 * value);
   if (new_value == m_current_value) {
     return;
   }
   m_current_value = new_value;
 
+  double new_value_percent = new_value / 10.0;
+
   size_t first_part_width = m_header.size() + 10;
   if (first_part_width + 10 > m_width) {
     // The progress bar would be less than 10 characters, so just print the
     // percentage.
-    PRINT(stdout, "\r{} {:5.1f}%", m_header, 100 * value);
+    PRINT(stdout, "\r{} {:5.1f}%", m_header, new_value_percent);
   } else {
     size_t total_bar_width = m_width - first_part_width;
-    size_t filled_bar_width = value * total_bar_width;
+    size_t filled_bar_width = (new_value_percent / 100) * total_bar_width;
     size_t unfilled_bar_width = total_bar_width - filled_bar_width;
     PRINT(stdout,
           "\r{} {:5.1f}% [{:=<{}}{: <{}}]",
           m_header,
-          100 * value,
+          new_value / 10.0,
           "",
           filled_bar_width,
           "",
index 2d32ac6..acfa2e3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #include "SignalHandler.hpp"
 
-#ifndef _WIN32
+#include "Context.hpp"
+#include "assertions.hpp"
 
-#  include "Context.hpp"
-#  include "assertions.hpp"
-
-#  include <signal.h> // NOLINT: sigaddset et al are defined in signal.h
-#  include <sys/types.h>
-#  include <sys/wait.h>
-#  include <unistd.h>
+#include <signal.h> // NOLINT: sigaddset et al are defined in signal.h
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
 
 namespace {
 
 SignalHandler* g_the_signal_handler = nullptr;
 sigset_t g_fatal_signal_set;
 
+const int k_handled_signals[] = {
+  SIGINT,
+  SIGTERM,
+#ifdef SIGHUP
+  SIGHUP,
+#endif
+#ifdef SIGQUIT
+  SIGQUIT,
+#endif
+};
+
 void
 register_signal_handler(int signum)
 {
-  struct sigaction act;
-  memset(&act, 0, sizeof(act));
+  struct sigaction act = {};
   act.sa_handler = SignalHandler::on_signal;
   act.sa_mask = g_fatal_signal_set;
-#  ifdef SA_RESTART
+#ifdef SA_RESTART
   act.sa_flags = SA_RESTART;
-#  endif
+#endif
+  sigaction(signum, &act, nullptr);
+}
+
+void
+deregister_signal_handler(int signum)
+{
+  struct sigaction act = {};
+  act.sa_handler = SIG_DFL;
   sigaction(signum, &act, nullptr);
 }
 
@@ -54,23 +70,13 @@ SignalHandler::SignalHandler(Context& ctx) : m_ctx(ctx)
   g_the_signal_handler = this;
 
   sigemptyset(&g_fatal_signal_set);
-  sigaddset(&g_fatal_signal_set, SIGINT);
-  sigaddset(&g_fatal_signal_set, SIGTERM);
-#  ifdef SIGHUP
-  sigaddset(&g_fatal_signal_set, SIGHUP);
-#  endif
-#  ifdef SIGQUIT
-  sigaddset(&g_fatal_signal_set, SIGQUIT);
-#  endif
-
-  register_signal_handler(SIGINT);
-  register_signal_handler(SIGTERM);
-#  ifdef SIGHUP
-  register_signal_handler(SIGHUP);
-#  endif
-#  ifdef SIGQUIT
-  register_signal_handler(SIGQUIT);
-#  endif
+  for (int signum : k_handled_signals) {
+    sigaddset(&g_fatal_signal_set, signum);
+  }
+
+  for (int signum : k_handled_signals) {
+    register_signal_handler(signum);
+  }
 
   signal(SIGPIPE, SIG_IGN); // NOLINT: This is no error, clang-tidy
 }
@@ -78,6 +84,11 @@ SignalHandler::SignalHandler(Context& ctx) : m_ctx(ctx)
 SignalHandler::~SignalHandler()
 {
   ASSERT(g_the_signal_handler);
+
+  for (int signum : k_handled_signals) {
+    deregister_signal_handler(signum);
+  }
+
   g_the_signal_handler = nullptr;
 }
 
@@ -110,34 +121,18 @@ SignalHandler::on_signal(int signum)
   kill(getpid(), signum);
 }
 
-#else // !_WIN32
-
-SignalHandler::SignalHandler(Context& ctx) : m_ctx(ctx)
-{
-}
-
-SignalHandler::~SignalHandler()
-{
-}
-
-#endif // !_WIN32
-
 void
 SignalHandler::block_signals()
 {
-#ifndef _WIN32
   sigprocmask(SIG_BLOCK, &g_fatal_signal_set, nullptr);
-#endif
 }
 
 void
 SignalHandler::unblock_signals()
 {
-#ifndef _WIN32
   sigset_t empty;
   sigemptyset(&empty);
   sigprocmask(SIG_SETMASK, &empty, nullptr);
-#endif
 }
 
 SignalHandlerBlocker::SignalHandlerBlocker()
index 603dbe2..b75e466 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -31,7 +31,9 @@ public:
   static void unblock_signals();
 
 private:
+#ifndef _WIN32
   Context& m_ctx;
+#endif
 };
 
 class SignalHandlerBlocker
@@ -40,3 +42,21 @@ public:
   SignalHandlerBlocker();
   ~SignalHandlerBlocker();
 };
+
+#ifdef _WIN32
+inline SignalHandler::SignalHandler(Context&)
+{
+}
+
+inline SignalHandler::~SignalHandler()
+{
+}
+
+inline SignalHandlerBlocker::SignalHandlerBlocker()
+{
+}
+
+inline SignalHandlerBlocker::~SignalHandlerBlocker()
+{
+}
+#endif
index 356bd92..15293e1 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -206,6 +206,7 @@ win32_lstat(const char* path, Stat::stat_t* st)
 Stat::Stat(StatFunction stat_function,
            const std::string& path,
            Stat::OnError on_error)
+  : m_path(path)
 {
   int result = stat_function(path.c_str(), &m_stat);
   if (result == 0) {
index 5c81a5b..5452872 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -20,6 +20,7 @@
 
 #include <core/wincompat.hpp>
 #include <util/TimePoint.hpp>
+#include <util/file.hpp>
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -116,6 +117,9 @@ public:
   // otherwise false.
   operator bool() const;
 
+  // Return the path that this stat result refers to.
+  const std::string& path() const;
+
   // Return whether this object refers to the same device and i-node as `other`
   // does.
   bool same_inode_as(const Stat& other) const;
@@ -148,6 +152,7 @@ protected:
   Stat(StatFunction stat_function, const std::string& path, OnError on_error);
 
 private:
+  std::string m_path;
   stat_t m_stat;
   int m_errno;
 
@@ -170,6 +175,12 @@ Stat::same_inode_as(const Stat& other) const
   return m_errno == 0 && device() == other.device() && inode() == other.inode();
 }
 
+inline const std::string&
+Stat::path() const
+{
+  return m_path;
+}
+
 inline int
 Stat::error_number() const
 {
@@ -240,7 +251,7 @@ inline uint64_t
 Stat::size_on_disk() const
 {
 #ifdef _WIN32
-  return (size() + 1023) & ~1023;
+  return util::likely_size_on_disk(size());
 #else
   return m_stat.st_blocks * 512;
 #endif
index e802a83..6ba1b34 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -525,9 +525,11 @@ format_argv_for_logging(const char* const* argv)
     if (i != 0) {
       result += ' ';
     }
-    for (const char* arg = argv[i]; *arg; ++arg) {
-      result += *arg;
+    std::string arg(argv[i]);
+    if (arg.empty() || arg.find(' ') != std::string::npos) {
+      arg = FMT("\"{}\"", arg);
     }
+    result += arg;
   }
   return result;
 }
@@ -555,30 +557,6 @@ format_base32hex(const uint8_t* data, size_t size)
   return result;
 }
 
-std::string
-format_human_readable_size(uint64_t size)
-{
-  if (size >= 1000 * 1000 * 1000) {
-    return FMT("{:.1f} GB", size / ((double)(1000 * 1000 * 1000)));
-  } else if (size >= 1000 * 1000) {
-    return FMT("{:.1f} MB", size / ((double)(1000 * 1000)));
-  } else {
-    return FMT("{:.1f} kB", size / 1000.0);
-  }
-}
-
-std::string
-format_parsable_size_with_suffix(uint64_t size)
-{
-  if (size >= 1000 * 1000 * 1000) {
-    return FMT("{:.1f}G", size / ((double)(1000 * 1000 * 1000)));
-  } else if (size >= 1000 * 1000) {
-    return FMT("{:.1f}M", size / ((double)(1000 * 1000)));
-  } else {
-    return FMT("{}", size);
-  }
-}
-
 void
 ensure_dir_exists(std::string_view dir)
 {
@@ -849,9 +827,9 @@ make_relative_path(const std::string& base_dir,
   const auto path_suffix = std::string(original_path.substr(path.length()));
   const auto real_path = Util::real_path(std::string(path));
 
-  const auto add_relpath_candidates = [&](auto path) {
+  const auto add_relpath_candidates = [&](auto p) {
     const std::string normalized_path =
-      Util::normalize_abstract_absolute_path(path);
+      Util::normalize_abstract_absolute_path(p);
     relpath_candidates.push_back(
       Util::get_relative_path(actual_cwd, normalized_path));
     if (apparent_cwd != actual_cwd) {
@@ -899,20 +877,14 @@ matches_dir_prefix_or_file(std::string_view dir_prefix_or_file,
              || is_dir_separator(dir_prefix_or_file.back()));
 }
 
-std::string
-normalize_abstract_absolute_path(std::string_view path)
+static std::string
+do_normalize_abstract_absolute_path(std::string_view path)
 {
   if (!util::is_absolute_path(path)) {
     return std::string(path);
   }
 
 #ifdef _WIN32
-  if (path.find("\\") != std::string_view::npos) {
-    std::string new_path(path);
-    std::replace(new_path.begin(), new_path.end(), '\\', '/');
-    return normalize_abstract_absolute_path(new_path);
-  }
-
   std::string drive(path.substr(0, 2));
   path = path.substr(2);
 #endif
@@ -960,10 +932,23 @@ normalize_abstract_absolute_path(std::string_view path)
 }
 
 std::string
+normalize_abstract_absolute_path(std::string_view path)
+{
+#ifdef _WIN32
+  std::string new_path(path);
+  std::replace(new_path.begin(), new_path.end(), '\\', '/');
+  return do_normalize_abstract_absolute_path(new_path);
+#else
+  return do_normalize_abstract_absolute_path(path);
+#endif
+}
+
+std::string
 normalize_concrete_absolute_path(const std::string& path)
 {
   const auto normalized_path = normalize_abstract_absolute_path(path);
-  return Stat::stat(normalized_path).same_inode_as(Stat::stat(path))
+  return (normalized_path == path
+          || Stat::stat(normalized_path).same_inode_as(Stat::stat(path)))
            ? normalized_path
            : path;
 }
@@ -995,47 +980,6 @@ parse_duration(std::string_view duration)
   }
 }
 
-uint64_t
-parse_size(const std::string& value)
-{
-  errno = 0;
-
-  char* p;
-  double result = strtod(value.c_str(), &p);
-  if (errno != 0 || result < 0 || p == value.c_str() || value.empty()) {
-    throw core::Error(FMT("invalid size: \"{}\"", value));
-  }
-
-  while (isspace(*p)) {
-    ++p;
-  }
-
-  if (*p != '\0') {
-    unsigned multiplier = *(p + 1) == 'i' ? 1024 : 1000;
-    switch (*p) {
-    case 'T':
-      result *= multiplier;
-      [[fallthrough]];
-    case 'G':
-      result *= multiplier;
-      [[fallthrough]];
-    case 'M':
-      result *= multiplier;
-      [[fallthrough]];
-    case 'K':
-    case 'k':
-      result *= multiplier;
-      break;
-    default:
-      throw core::Error(FMT("invalid size: \"{}\"", value));
-    }
-  } else {
-    // Default suffix: G.
-    result *= 1000 * 1000 * 1000;
-  }
-  return static_cast<uint64_t>(result);
-}
-
 #ifndef _WIN32
 std::string
 read_link(const std::string& path)
index e29aff3..d1a4796 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -143,12 +143,6 @@ std::string format_base16(const uint8_t* data, size_t size);
 // padding characters will be added.
 std::string format_base32hex(const uint8_t* data, size_t size);
 
-// Format `size` as a human-readable string.
-std::string format_human_readable_size(uint64_t size);
-
-// Format `size` as a parsable string.
-std::string format_parsable_size_with_suffix(uint64_t size);
-
 // Return current working directory (CWD) as returned from getcwd(3) (i.e.,
 // normalized path without symlink parts). Returns the empty string on error.
 std::string get_actual_cwd();
@@ -283,11 +277,6 @@ std::string normalize_concrete_absolute_path(const std::string& path);
 // into seconds. Throws `core::Error` on error.
 uint64_t parse_duration(std::string_view duration);
 
-// Parse a "size value", i.e. a string that can end in k, M, G, T (10-based
-// suffixes) or Ki, Mi, Gi, Ti (2-based suffixes). For backward compatibility, K
-// is also recognized as a synonym of k. Throws `core::Error` on parse error.
-uint64_t parse_size(const std::string& value);
-
 #ifndef _WIN32
 // Like readlink(2) but returns the string (or the empty string on failure).
 std::string read_link(const std::string& path);
index 0bc37e5..6e38b4b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -71,8 +71,9 @@ struct ArgumentProcessingState
   bool found_md_or_mmd_opt = false;
   bool found_Wa_a_opt = false;
 
-  std::string explicit_language;    // As specified with -x.
-  std::string input_charset_option; // -finput-charset=...
+  std::string explicit_language;             // As specified with -x.
+  std::string input_charset_option;          // -finput-charset=...
+  std::string last_seen_msvc_z_debug_option; // /Z7, /Zi or /ZI
 
   // Is the dependency file set via -Wp,-M[M]D,target or -MFtarget?
   OutputDepOrigin output_dep_origin = OutputDepOrigin::none;
@@ -264,6 +265,26 @@ process_profiling_option(const Context& ctx,
   return true;
 }
 
+std::string
+make_dash_option(const Config& config, const std::string& arg)
+{
+  std::string new_arg = arg;
+  if (config.is_compiler_group_msvc() && util::starts_with(arg, "/")) {
+    // MSVC understands both /option and -option, so convert all /option to
+    // -option to simplify our handling.
+    new_arg[0] = '-';
+  }
+  return new_arg;
+}
+
+bool
+is_msvc_z_debug_option(std::string_view arg)
+{
+  static const char* debug_options[] = {"-Z7", "-ZI", "-Zi"};
+  return std::find(std::begin(debug_options), std::end(debug_options), arg)
+         != std::end(debug_options);
+}
+
 // Returns std::nullopt if the option wasn't recognized, otherwise the error
 // code (with Statistic::none for "no error").
 std::optional<Statistic>
@@ -286,17 +307,18 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  bool changed_from_slash = false;
-  if (ctx.config.is_compiler_group_msvc() && util::starts_with(args[i], "/")) {
-    // MSVC understands both /option and -option, so convert all /option to
-    // -option to simplify our handling.
-    args[i][0] = '-';
-    changed_from_slash = true;
+  // arg should only be used when detecting options. It should not be added to
+  // state.*_args since it's potentially != args[i].
+  std::string arg = make_dash_option(ctx.config, args[i]);
+
+  // Exit early if we notice a non-option argument right away.
+  if (arg.empty() || (arg[0] != '-' && arg[0] != '@')) {
+    return std::nullopt;
   }
 
   // Ignore clang -ivfsoverlay <arg> to not detect multiple input files.
-  if (args[i] == "-ivfsoverlay"
-      && !(config.sloppiness().is_enabled(core::Sloppy::ivfsoverlay))) {
+  if (arg == "-ivfsoverlay"
+      && !(config.sloppiness().contains(core::Sloppy::ivfsoverlay))) {
     LOG_RAW(
       "You have to specify \"ivfsoverlay\" sloppiness when using"
       " -ivfsoverlay to get hits");
@@ -304,17 +326,17 @@ process_option_arg(const Context& ctx,
   }
 
   // Special case for -E.
-  if (args[i] == "-E") {
+  if (arg == "-E") {
     return Statistic::called_for_preprocessing;
   }
   // MSVC -P is -E with output to a file.
-  if (args[i] == "-P" && ctx.config.is_compiler_group_msvc()) {
+  if (arg == "-P" && ctx.config.is_compiler_group_msvc()) {
     return Statistic::called_for_preprocessing;
   }
 
   // Handle "@file" argument.
-  if (util::starts_with(args[i], "@") || util::starts_with(args[i], "-@")) {
-    const char* argpath = args[i].c_str() + 1;
+  if (util::starts_with(arg, "@") || util::starts_with(arg, "-@")) {
+    const char* argpath = arg.c_str() + 1;
 
     if (argpath[-1] == '-') {
       ++argpath;
@@ -335,7 +357,7 @@ process_option_arg(const Context& ctx,
 
   // Handle cuda "-optf" and "--options-file" argument.
   if (config.compiler_type() == CompilerType::nvcc
-      && (args[i] == "-optf" || args[i] == "--options-file")) {
+      && (arg == "-optf" || arg == "--options-file")) {
     if (i == args.size() - 1) {
       LOG("Expected argument after {}", args[i]);
       return Statistic::bad_compiler_arguments;
@@ -358,26 +380,25 @@ process_option_arg(const Context& ctx,
   }
 
   // These are always too hard.
-  if (compopt_too_hard(args[i]) || util::starts_with(args[i], "-fdump-")
-      || util::starts_with(args[i], "-MJ")
-      || util::starts_with(args[i], "-Yc")) {
+  if (compopt_too_hard(arg) || util::starts_with(arg, "-fdump-")
+      || util::starts_with(arg, "-MJ") || util::starts_with(arg, "-Yc")) {
     LOG("Compiler option {} is unsupported", args[i]);
     return Statistic::unsupported_compiler_option;
   }
 
   // These are too hard in direct mode.
-  if (config.direct_mode() && compopt_too_hard_for_direct_mode(args[i])) {
+  if (config.direct_mode() && compopt_too_hard_for_direct_mode(arg)) {
     LOG("Unsupported compiler option for direct mode: {}", args[i]);
     config.set_direct_mode(false);
   }
 
   // -Xarch_* options are too hard.
-  if (util::starts_with(args[i], "-Xarch_")) {
+  if (util::starts_with(arg, "-Xarch_")) {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
-    const auto arch = args[i].substr(7);
+    const auto arch = arg.substr(7);
     if (!state.found_xarch_arch) {
       state.found_xarch_arch = arch;
     } else if (*state.found_xarch_arch != arch) {
@@ -389,7 +410,7 @@ process_option_arg(const Context& ctx,
   }
 
   // Handle -arch options.
-  if (args[i] == "-arch") {
+  if (arg == "-arch") {
     ++i;
     args_info.arch_args.emplace_back(args[i]);
     if (args_info.arch_args.size() == 2) {
@@ -401,7 +422,7 @@ process_option_arg(const Context& ctx,
   // Some arguments that clang passes directly to cc1 (related to precompiled
   // headers) need the usual ccache handling. In those cases, the -Xclang
   // prefix is skipped and the cc1 argument is handled instead.
-  if (args[i] == "-Xclang" && i + 1 < args.size()
+  if (arg == "-Xclang" && i + 1 < args.size()
       && (args[i + 1] == "-emit-pch" || args[i + 1] == "-emit-pth"
           || args[i + 1] == "-include-pch" || args[i + 1] == "-include-pth"
           || args[i + 1] == "-fno-pch-timestamp")) {
@@ -413,10 +434,11 @@ process_option_arg(const Context& ctx,
       state.common_args.push_back(args[i]);
     }
     ++i;
+    arg = make_dash_option(ctx.config, args[i]);
   }
 
-  if (util::starts_with(args[i], "-Wa,")) {
-    for (const auto part : util::Tokenizer(&args[i][4], ",")) {
+  if (util::starts_with(arg, "-Wa,")) {
+    for (const auto part : util::Tokenizer(&arg[4], ",")) {
       if (util::starts_with(part, "-a")) {
         if (state.found_Wa_a_opt) {
           LOG_RAW(
@@ -434,11 +456,10 @@ process_option_arg(const Context& ctx,
   }
 
   // Handle options that should not be passed to the preprocessor.
-  if (compopt_affects_compiler_output(args[i])) {
+  if (compopt_affects_compiler_output(arg)) {
     state.compiler_only_args.push_back(args[i]);
-    if (compopt_takes_arg(args[i])
-        || (config.compiler_type() == CompilerType::nvcc
-            && args[i] == "-Werror")) {
+    if (compopt_takes_arg(arg)
+        || (config.compiler_type() == CompilerType::nvcc && arg == "-Werror")) {
       if (i == args.size() - 1) {
         LOG("Missing argument to {}", args[i]);
         return Statistic::bad_compiler_arguments;
@@ -448,7 +469,7 @@ process_option_arg(const Context& ctx,
     }
     return Statistic::none;
   }
-  if (compopt_prefix_affects_compiler_output(args[i])) {
+  if (compopt_prefix_affects_compiler_output(arg)) {
     state.compiler_only_args.push_back(args[i]);
     return Statistic::none;
   }
@@ -461,12 +482,12 @@ process_option_arg(const Context& ctx,
   // result in an object file that would be different from the actual
   // compilation (even though it should be compatible), so require a sloppiness
   // flag.
-  if (args[i] == "-fmodules") {
+  if (arg == "-fmodules") {
     if (!config.depend_mode() || !config.direct_mode()) {
       LOG("Compiler option {} is unsupported without direct depend mode",
           args[i]);
       return Statistic::could_not_use_modules;
-    } else if (!(config.sloppiness().is_enabled(core::Sloppy::modules))) {
+    } else if (!(config.sloppiness().contains(core::Sloppy::modules))) {
       LOG_RAW(
         "You have to specify \"modules\" sloppiness when using"
         " -fmodules to get hits");
@@ -475,33 +496,33 @@ process_option_arg(const Context& ctx,
   }
 
   // We must have -c.
-  if (args[i] == "-c") {
+  if (arg == "-c") {
     state.found_c_opt = true;
     return Statistic::none;
   }
 
   // MSVC -Fo with no space.
-  if (util::starts_with(args[i], "-Fo") && config.is_compiler_group_msvc()) {
-    args_info.output_obj = args[i].substr(3);
+  if (util::starts_with(arg, "-Fo") && config.is_compiler_group_msvc()) {
+    args_info.output_obj = arg.substr(3);
     return Statistic::none;
   }
 
   // when using nvcc with separable compilation, -dc implies -c
-  if ((args[i] == "-dc" || args[i] == "--device-c")
+  if ((arg == "-dc" || arg == "--device-c")
       && config.compiler_type() == CompilerType::nvcc) {
     state.found_dc_opt = true;
     return Statistic::none;
   }
 
   // -S changes the default extension.
-  if (args[i] == "-S") {
+  if (arg == "-S") {
     state.common_args.push_back(args[i]);
     state.found_S_opt = true;
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "-x")) {
-    if (args[i].length() >= 3 && !islower(args[i][2])) {
+  if (util::starts_with(arg, "-x")) {
+    if (arg.length() >= 3 && !islower(arg[2])) {
       // -xCODE (where CODE can be e.g. Host or CORE-AVX2, always starting with
       // an uppercase letter) is an ordinary Intel compiler option, not a
       // language specification. (GCC's "-x" language argument is always
@@ -512,7 +533,7 @@ process_option_arg(const Context& ctx,
 
     // Special handling for -x: remember the last specified language before the
     // input file and strip all -x options from the arguments.
-    if (args[i].length() == 2) {
+    if (arg.length() == 2) {
       if (i == args.size() - 1) {
         LOG("Missing argument to {}", args[i]);
         return Statistic::bad_compiler_arguments;
@@ -524,15 +545,15 @@ process_option_arg(const Context& ctx,
       return Statistic::none;
     }
 
-    DEBUG_ASSERT(args[i].length() >= 3);
+    DEBUG_ASSERT(arg.length() >= 3);
     if (args_info.input_file.empty()) {
-      state.explicit_language = args[i].substr(2);
+      state.explicit_language = arg.substr(2);
     }
     return Statistic::none;
   }
 
   // We need to work out where the output was meant to go.
-  if (args[i] == "-o") {
+  if (arg == "-o") {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
@@ -546,16 +567,16 @@ process_option_arg(const Context& ctx,
   // Cl does support it as deprecated, but also has -openmp or -link -out
   // which can confuse this and cause incorrect output_obj (and thus
   // ccache debug file location), so better ignore it.
-  if (util::starts_with(args[i], "-o")
+  if (util::starts_with(arg, "-o")
       && config.compiler_type() != CompilerType::nvcc
       && config.compiler_type() != CompilerType::msvc) {
-    args_info.output_obj = args[i].substr(2);
+    args_info.output_obj = arg.substr(2);
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "-fdebug-prefix-map=")
-      || util::starts_with(args[i], "-ffile-prefix-map=")) {
-    std::string map = args[i].substr(args[i].find('=') + 1);
+  if (util::starts_with(arg, "-fdebug-prefix-map=")
+      || util::starts_with(arg, "-ffile-prefix-map=")) {
+    std::string map = arg.substr(arg.find('=') + 1);
     args_info.debug_prefix_maps.push_back(map);
     state.common_args.push_back(args[i]);
     return Statistic::none;
@@ -563,22 +584,22 @@ process_option_arg(const Context& ctx,
 
   // Debugging is handled specially, so that we know if we can strip line
   // number info.
-  if (util::starts_with(args[i], "-g")) {
+  if (util::starts_with(arg, "-g")) {
     state.common_args.push_back(args[i]);
 
-    if (util::starts_with(args[i], "-gdwarf")) {
+    if (util::starts_with(arg, "-gdwarf")) {
       // Selection of DWARF format (-gdwarf or -gdwarf-<version>) enables
       // debug info on level 2.
       args_info.generating_debuginfo = true;
       return Statistic::none;
     }
 
-    if (util::starts_with(args[i], "-gz")) {
+    if (util::starts_with(arg, "-gz")) {
       // -gz[=type] neither disables nor enables debug info.
       return Statistic::none;
     }
 
-    char last_char = args[i].back();
+    char last_char = arg.back();
     if (last_char == '0') {
       // "-g0", "-ggdb0" or similar: All debug information disabled.
       args_info.generating_debuginfo = false;
@@ -588,28 +609,34 @@ process_option_arg(const Context& ctx,
       if (last_char == '3') {
         state.generating_debuginfo_level_3 = true;
       }
-      if (args[i] == "-gsplit-dwarf") {
+      if (arg == "-gsplit-dwarf") {
         args_info.seen_split_dwarf = true;
       }
     }
     return Statistic::none;
   }
 
+  if (config.is_compiler_group_msvc() && !config.is_compiler_group_clang()
+      && is_msvc_z_debug_option(arg)) {
+    state.last_seen_msvc_z_debug_option = args[i];
+    state.common_args.push_back(args[i]);
+    return Statistic::none;
+  }
+
   // These options require special handling, because they behave differently
   // with gcc -E, when the output file is not specified.
-  if ((args[i] == "-MD" || args[i] == "-MMD")
-      && !config.is_compiler_group_msvc()) {
+  if ((arg == "-MD" || arg == "-MMD") && !config.is_compiler_group_msvc()) {
     state.found_md_or_mmd_opt = true;
     args_info.generating_dependencies = true;
     state.dep_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "-MF")) {
+  if (util::starts_with(arg, "-MF")) {
     state.found_mf_opt = true;
 
     std::string dep_file;
-    bool separate_argument = (args[i].size() == 3);
+    bool separate_argument = (arg.size() == 3);
     if (separate_argument) {
       // -MF arg
       if (i == args.size() - 1) {
@@ -620,7 +647,7 @@ process_option_arg(const Context& ctx,
       i++;
     } else {
       // -MFarg or -MF=arg (EDG-based compilers)
-      dep_file = args[i].substr(args[i][3] == '=' ? 4 : 3);
+      dep_file = arg.substr(arg[3] == '=' ? 4 : 3);
     }
 
     if (state.output_dep_origin <= OutputDepOrigin::mf) {
@@ -637,12 +664,12 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if ((util::starts_with(args[i], "-MQ") || util::starts_with(args[i], "-MT"))
+  if ((util::starts_with(arg, "-MQ") || util::starts_with(arg, "-MT"))
       && !config.is_compiler_group_msvc()) {
-    const bool is_mq = args[i][2] == 'Q';
+    const bool is_mq = arg[2] == 'Q';
 
     std::string_view dep_target;
-    if (args[i].size() == 3) {
+    if (arg.size() == 3) {
       // -MQ arg or -MT arg
       if (i == args.size() - 1) {
         LOG("Missing argument to {}", args[i]);
@@ -654,7 +681,7 @@ process_option_arg(const Context& ctx,
       i++;
     } else {
       // -MQarg or -MTarg
-      const std::string_view arg_view(args[i]);
+      const std::string_view arg_view(arg);
       const auto arg_opt = arg_view.substr(0, 3);
       dep_target = arg_view.substr(3);
       state.dep_args.push_back(FMT("{}{}", arg_opt, dep_target));
@@ -674,56 +701,56 @@ process_option_arg(const Context& ctx,
   // MSVC -MD[d], -MT[d] and -LT[d] options are something different than GCC's
   // -MD etc.
   if (config.is_compiler_group_msvc()
-      && (util::starts_with(args[i], "-MD") || util::starts_with(args[i], "-MT")
-          || util::starts_with(args[i], "-LD"))) {
+      && (util::starts_with(arg, "-MD") || util::starts_with(arg, "-MT")
+          || util::starts_with(arg, "-LD"))) {
     // These affect compiler but also #define some things.
     state.cpp_args.push_back(args[i]);
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (args[i] == "-showIncludes") {
+  if (arg == "-showIncludes") {
     args_info.generating_includes = true;
     state.dep_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (args[i] == "-fprofile-arcs") {
+  if (arg == "-fprofile-arcs") {
     args_info.profile_arcs = true;
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (args[i] == "-ftest-coverage") {
+  if (arg == "-ftest-coverage") {
     args_info.generating_coverage = true;
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (args[i] == "-fstack-usage") {
+  if (arg == "-fstack-usage") {
     args_info.generating_stackusage = true;
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
   // -Zs is MSVC's -fsyntax-only equivalent
-  if (args[i] == "-fsyntax-only" || args[i] == "-Zs") {
+  if (arg == "-fsyntax-only" || arg == "-Zs") {
     args_info.expect_output_obj = false;
     state.compiler_only_args.push_back(args[i]);
     state.found_syntax_only = true;
     return Statistic::none;
   }
 
-  if (args[i] == "--coverage"      // = -fprofile-arcs -ftest-coverage
-      || args[i] == "-coverage") { // Undocumented but still works.
+  if (arg == "--coverage"      // = -fprofile-arcs -ftest-coverage
+      || arg == "-coverage") { // Undocumented but still works.
     args_info.profile_arcs = true;
     args_info.generating_coverage = true;
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (args[i] == "-fprofile-abs-path") {
-    if (!config.sloppiness().is_enabled(core::Sloppy::gcno_cwd)) {
+  if (arg == "-fprofile-abs-path") {
+    if (!config.sloppiness().contains(core::Sloppy::gcno_cwd)) {
       // -fprofile-abs-path makes the compiler include absolute paths based on
       // the actual CWD in the .gcno file.
       state.hash_actual_cwd = true;
@@ -731,10 +758,10 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "-fprofile-")
-      || util::starts_with(args[i], "-fauto-profile")
-      || args[i] == "-fbranch-probabilities") {
-    if (!process_profiling_option(ctx, args_info, args[i])) {
+  if (util::starts_with(arg, "-fprofile-")
+      || util::starts_with(arg, "-fauto-profile")
+      || arg == "-fbranch-probabilities") {
+    if (!process_profiling_option(ctx, args_info, arg)) {
       // The failure is logged by process_profiling_option.
       return Statistic::unsupported_compiler_option;
     }
@@ -742,21 +769,21 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "-fsanitize-blacklist=")) {
+  if (util::starts_with(arg, "-fsanitize-blacklist=")) {
     args_info.sanitize_blacklists.emplace_back(args[i].substr(21));
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "--sysroot=")) {
-    auto path = std::string_view(args[i]).substr(10);
+  if (util::starts_with(arg, "--sysroot=")) {
+    auto path = std::string_view(arg).substr(10);
     auto relpath = Util::make_relative_path(ctx, path);
     state.common_args.push_back("--sysroot=" + relpath);
     return Statistic::none;
   }
 
   // Alternate form of specifying sysroot without =
-  if (args[i] == "--sysroot") {
+  if (arg == "--sysroot") {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
@@ -769,7 +796,7 @@ process_option_arg(const Context& ctx,
   }
 
   // Alternate form of specifying target without =
-  if (args[i] == "-target") {
+  if (arg == "-target") {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
@@ -780,7 +807,7 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if (args[i] == "-P" || args[i] == "-Wp,-P") {
+  if (arg == "-P" || arg == "-Wp,-P") {
     // Avoid passing -P to the preprocessor since it removes preprocessor
     // information we need.
     state.compiler_only_args.push_back(args[i]);
@@ -789,43 +816,41 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if (util::starts_with(args[i], "-Wp,")) {
-    if (args[i].find(",-P,") != std::string::npos
-        || util::ends_with(args[i], ",-P")) {
+  if (util::starts_with(arg, "-Wp,")) {
+    if (arg.find(",-P,") != std::string::npos || util::ends_with(arg, ",-P")) {
       LOG("-P together with other preprocessor options is too hard: {}",
           args[i]);
       return Statistic::unsupported_compiler_option;
-    } else if (util::starts_with(args[i], "-Wp,-MD,")
-               && args[i].find(',', 8) == std::string::npos) {
+    } else if (util::starts_with(arg, "-Wp,-MD,")
+               && arg.find(',', 8) == std::string::npos) {
       state.found_wp_md_or_mmd_opt = true;
       args_info.generating_dependencies = true;
       if (state.output_dep_origin <= OutputDepOrigin::wp) {
         state.output_dep_origin = OutputDepOrigin::wp;
-        args_info.output_dep = args[i].substr(8);
+        args_info.output_dep = arg.substr(8);
       }
       state.dep_args.push_back(args[i]);
       return Statistic::none;
-    } else if (util::starts_with(args[i], "-Wp,-MMD,")
-               && args[i].find(',', 9) == std::string::npos) {
+    } else if (util::starts_with(arg, "-Wp,-MMD,")
+               && arg.find(',', 9) == std::string::npos) {
       state.found_wp_md_or_mmd_opt = true;
       args_info.generating_dependencies = true;
       if (state.output_dep_origin <= OutputDepOrigin::wp) {
         state.output_dep_origin = OutputDepOrigin::wp;
-        args_info.output_dep = args[i].substr(9);
+        args_info.output_dep = arg.substr(9);
       }
       state.dep_args.push_back(args[i]);
       return Statistic::none;
-    } else if (util::starts_with(args[i], "-Wp,-D")
-               && args[i].find(',', 6) == std::string::npos) {
-      // Treat it like -D.
-      state.cpp_args.push_back(args[i].substr(4));
+    } else if ((util::starts_with(arg, "-Wp,-D")
+                || util::starts_with(arg, "-Wp,-U"))
+               && arg.find(',', 6) == std::string::npos) {
+      state.cpp_args.push_back(args[i]);
       return Statistic::none;
-    } else if (args[i] == "-Wp,-MP"
-               || (args[i].size() > 8 && util::starts_with(args[i], "-Wp,-M")
-                   && args[i][7] == ','
-                   && (args[i][6] == 'F' || args[i][6] == 'Q'
-                       || args[i][6] == 'T')
-                   && args[i].find(',', 8) == std::string::npos)) {
+    } else if (arg == "-Wp,-MP"
+               || (arg.size() > 8 && util::starts_with(arg, "-Wp,-M")
+                   && arg[7] == ','
+                   && (arg[6] == 'F' || arg[6] == 'Q' || arg[6] == 'T')
+                   && arg.find(',', 8) == std::string::npos)) {
       state.dep_args.push_back(args[i]);
       return Statistic::none;
     } else if (config.direct_mode()) {
@@ -840,18 +865,18 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if (args[i] == "-MP") {
+  if (arg == "-MP") {
     state.dep_args.push_back(args[i]);
     return Statistic::none;
   }
 
   // Input charset needs to be handled specially.
-  if (util::starts_with(args[i], "-finput-charset=")) {
+  if (util::starts_with(arg, "-finput-charset=")) {
     state.input_charset_option = args[i];
     return Statistic::none;
   }
 
-  if (args[i] == "--serialize-diagnostics") {
+  if (arg == "--serialize-diagnostics") {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
@@ -863,17 +888,16 @@ process_option_arg(const Context& ctx,
   }
 
   if (config.compiler_type() == CompilerType::gcc) {
-    if (args[i] == "-fdiagnostics-color"
-        || args[i] == "-fdiagnostics-color=always") {
+    if (arg == "-fdiagnostics-color" || arg == "-fdiagnostics-color=always") {
       state.color_diagnostics = ColorDiagnostics::always;
       state.compiler_only_args_no_hash.push_back(args[i]);
       return Statistic::none;
-    } else if (args[i] == "-fno-diagnostics-color"
-               || args[i] == "-fdiagnostics-color=never") {
+    } else if (arg == "-fno-diagnostics-color"
+               || arg == "-fdiagnostics-color=never") {
       state.color_diagnostics = ColorDiagnostics::never;
       state.compiler_only_args_no_hash.push_back(args[i]);
       return Statistic::none;
-    } else if (args[i] == "-fdiagnostics-color=auto") {
+    } else if (arg == "-fdiagnostics-color=auto") {
       state.color_diagnostics = ColorDiagnostics::automatic;
       state.compiler_only_args_no_hash.push_back(args[i]);
       return Statistic::none;
@@ -881,16 +905,17 @@ process_option_arg(const Context& ctx,
   } else if (config.is_compiler_group_clang()) {
     // In the "-Xclang -fcolor-diagnostics" form, -Xclang is skipped and the
     // -fcolor-diagnostics argument which is passed to cc1 is handled below.
-    if (args[i] == "-Xclang" && i + 1 < args.size()
+    if (arg == "-Xclang" && i + 1 < args.size()
         && args[i + 1] == "-fcolor-diagnostics") {
       state.compiler_only_args_no_hash.push_back(args[i]);
       ++i;
+      arg = make_dash_option(ctx.config, args[i]);
     }
-    if (args[i] == "-fcolor-diagnostics") {
+    if (arg == "-fcolor-diagnostics") {
       state.color_diagnostics = ColorDiagnostics::always;
       state.compiler_only_args_no_hash.push_back(args[i]);
       return Statistic::none;
-    } else if (args[i] == "-fno-color-diagnostics") {
+    } else if (arg == "-fno-color-diagnostics") {
       state.color_diagnostics = ColorDiagnostics::never;
       state.compiler_only_args_no_hash.push_back(args[i]);
       return Statistic::none;
@@ -898,31 +923,31 @@ process_option_arg(const Context& ctx,
   }
 
   // GCC
-  if (args[i] == "-fdirectives-only") {
+  if (arg == "-fdirectives-only") {
     state.found_directives_only = true;
     return Statistic::none;
   }
 
   // Clang
-  if (args[i] == "-frewrite-includes") {
+  if (arg == "-frewrite-includes") {
     state.found_rewrite_includes = true;
     return Statistic::none;
   }
 
-  if (args[i] == "-fno-pch-timestamp") {
+  if (arg == "-fno-pch-timestamp") {
     args_info.fno_pch_timestamp = true;
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (args[i] == "-fpch-preprocess") {
+  if (arg == "-fpch-preprocess") {
     state.found_fpch_preprocess = true;
     state.common_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (config.sloppiness().is_enabled(core::Sloppy::clang_index_store)
-      && args[i] == "-index-store-path") {
+  if (config.sloppiness().contains(core::Sloppy::clang_index_store)
+      && arg == "-index-store-path") {
     // Xcode 9 or later calls Clang with this option. The given path includes a
     // UUID that might lead to cache misses, especially when cache is shared
     // among multiple users.
@@ -933,17 +958,17 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
-  if (args[i] == "-frecord-gcc-switches") {
+  if (arg == "-frecord-gcc-switches") {
     state.hash_full_command_line = true;
   }
 
   // MSVC -u is something else than GCC -u, handle it specially.
-  if (args[i] == "-u" && ctx.config.is_compiler_group_msvc()) {
+  if (arg == "-u" && ctx.config.is_compiler_group_msvc()) {
     state.cpp_args.push_back(args[i]);
     return Statistic::none;
   }
 
-  if (compopt_takes_path(args[i])) {
+  if (compopt_takes_path(arg)) {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
@@ -953,11 +978,8 @@ process_option_arg(const Context& ctx,
     // index further behind.
     const size_t next = args[i + 1] == "-Xclang" && i + 2 < args.size() ? 2 : 1;
 
-    if (!detect_pch(args[i],
-                    args[i + next],
-                    args_info.included_pch_file,
-                    next == 2,
-                    state)) {
+    if (!detect_pch(
+          arg, args[i + next], args_info.included_pch_file, next == 2, state)) {
       return Statistic::bad_compiler_arguments;
     }
 
@@ -966,7 +988,7 @@ process_option_arg(const Context& ctx,
     // produced by the compiler will be normalized.
     std::string relpath = Util::make_relative_path(ctx, args[i + next]);
     auto& dest_args =
-      compopt_affects_cpp_output(args[i]) ? state.cpp_args : state.common_args;
+      compopt_affects_cpp_output(arg) ? state.cpp_args : state.common_args;
     dest_args.push_back(args[i]);
     if (next == 2) {
       dest_args.push_back(args[i + 1]);
@@ -978,12 +1000,11 @@ process_option_arg(const Context& ctx,
   }
 
   // Detect PCH for options with concatenated path (relative or absolute).
-  if (util::starts_with(args[i], "-include")
-      || util::starts_with(args[i], "-Fp")
-      || util::starts_with(args[i], "-Yu")) {
-    const size_t path_pos = util::starts_with(args[i], "-include") ? 8 : 3;
-    if (!detect_pch(args[i].substr(0, path_pos),
-                    args[i].substr(path_pos),
+  if (util::starts_with(arg, "-include") || util::starts_with(arg, "-Fp")
+      || util::starts_with(arg, "-Yu")) {
+    const size_t path_pos = util::starts_with(arg, "-include") ? 8 : 3;
+    if (!detect_pch(arg.substr(0, path_pos),
+                    arg.substr(path_pos),
                     args_info.included_pch_file,
                     false,
                     state)) {
@@ -994,13 +1015,13 @@ process_option_arg(const Context& ctx,
   }
 
   // Potentially rewrite concatenated absolute path argument to relative.
-  if (args[i][0] == '-') {
-    const auto path_pos = Util::is_absolute_path_with_prefix(args[i]);
+  if (arg[0] == '-') {
+    const auto path_pos = Util::is_absolute_path_with_prefix(arg);
     if (path_pos) {
       const std::string option = args[i].substr(0, *path_pos);
       if (compopt_takes_concat_arg(option) && compopt_takes_path(option)) {
         const auto relpath = Util::make_relative_path(
-          ctx, std::string_view(args[i]).substr(*path_pos));
+          ctx, std::string_view(arg).substr(*path_pos));
         std::string new_option = option + relpath;
         if (compopt_affects_cpp_output(option)) {
           state.cpp_args.push_back(new_option);
@@ -1013,13 +1034,13 @@ process_option_arg(const Context& ctx,
   }
 
   // Options that take an argument.
-  if (compopt_takes_arg(args[i])) {
+  if (compopt_takes_arg(arg)) {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
 
-    if (compopt_affects_cpp_output(args[i])) {
+    if (compopt_affects_cpp_output(arg)) {
       state.cpp_args.push_back(args[i]);
       state.cpp_args.push_back(args[i + 1]);
     } else {
@@ -1037,21 +1058,22 @@ process_option_arg(const Context& ctx,
   }
 
   // Other options.
-  if (args[i][0] == '-') {
-    if (compopt_affects_cpp_output(args[i])
-        || compopt_prefix_affects_cpp_output(args[i])) {
+  if (arg[0] == '-') {
+    if (compopt_affects_cpp_output(arg)
+        || compopt_prefix_affects_cpp_output(arg)) {
       state.cpp_args.push_back(args[i]);
+      return Statistic::none;
+    } else if (ctx.config.is_compiler_group_msvc()
+               && args[i][0] == '/' // Intentionally not checking arg here
+               && Stat::stat(args[i])) {
+      // Likely the input file, which is handled in process_arg later.
     } else {
       state.common_args.push_back(args[i]);
+      return Statistic::none;
     }
-    return Statistic::none;
   }
 
   // It was not a known option.
-  if (changed_from_slash) {
-    args[i][0] = '/';
-  }
-
   return std::nullopt;
 }
 
@@ -1072,7 +1094,7 @@ process_arg(const Context& ctx,
 
   size_t& i = args_index;
 
-  // If an argument isn't a plain file then assume its an option, not an input
+  // If an argument isn't a plain file then assume it's an option, not an input
   // file. This allows us to cope better with unusual compiler options.
   //
   // Note that "/dev/null" is an exception that is sometimes used as an input
@@ -1154,6 +1176,14 @@ process_args(Context& ctx)
     return Statistic::unsupported_compiler_option;
   }
 
+  if (!state.last_seen_msvc_z_debug_option.empty()
+      && state.last_seen_msvc_z_debug_option.substr(2) != "7") {
+    // /Zi and /ZI are unsupported, but /Z7 is fine.
+    LOG("Compiler option {} is unsupported",
+        state.last_seen_msvc_z_debug_option);
+    return Statistic::unsupported_compiler_option;
+  }
+
   // Don't try to second guess the compiler's heuristics for stdout handling.
   if (args_info.output_obj == "-") {
     LOG_RAW("Output file is -");
@@ -1216,7 +1246,7 @@ process_args(Context& ctx)
 
   if (state.found_pch || state.found_fpch_preprocess) {
     args_info.using_precompiled_header = true;
-    if (!(config.sloppiness().is_enabled(core::Sloppy::time_macros))) {
+    if (!(config.sloppiness().contains(core::Sloppy::time_macros))) {
       LOG_RAW(
         "You have to specify \"time_macros\" sloppiness when using"
         " precompiled headers to get direct hits");
@@ -1254,7 +1284,7 @@ process_args(Context& ctx)
   }
 
   if (args_info.output_is_precompiled_header
-      && !(config.sloppiness().is_enabled(core::Sloppy::pch_defines))) {
+      && !(config.sloppiness().contains(core::Sloppy::pch_defines))) {
     LOG_RAW(
       "You have to specify \"pch_defines,time_macros\" sloppiness when"
       " creating precompiled headers");
index c063a58..58edac9 100644 (file)
@@ -1,5 +1,5 @@
 // Copyright (C) 2002-2007 Andrew Tridgell
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #include <algorithm>
 #include <cmath>
+#include <cstring>
 #include <limits>
 #include <memory>
+#include <unordered_map>
 
 using core::Statistic;
 
@@ -84,6 +86,14 @@ using core::Statistic;
 // stored in the cache changes in a backwards-incompatible way.
 const char HASH_PREFIX[] = "4";
 
+// Search for k_ccache_disable_token within the first
+// k_ccache_disable_search_limit bytes of the input file.
+const size_t k_ccache_disable_search_limit = 4096;
+
+// String to look for when checking whether to disable ccache for the input
+// file.
+const char k_ccache_disable_token[] = "ccache:disable";
+
 namespace {
 
 // Return nonstd::make_unexpected<Failure> if ccache did not succeed in getting
@@ -134,6 +144,14 @@ Failure::set_exit_code(const int exit_code)
 
 } // namespace
 
+static bool
+should_disable_ccache_for_input_file(const std::string& path)
+{
+  auto content =
+    util::read_file_part<std::string>(path, 0, k_ccache_disable_search_limit);
+  return content && content->find(k_ccache_disable_token) != std::string::npos;
+}
+
 static void
 add_prefix(const Context& ctx, Args& args, const std::string& prefix_command)
 {
@@ -263,14 +281,14 @@ include_file_too_new(const Context& ctx,
   // The comparison using >= is intentional, due to a possible race between
   // starting compilation and writing the include file. See also the notes under
   // "Performance" in doc/MANUAL.adoc.
-  if (!(ctx.config.sloppiness().is_enabled(core::Sloppy::include_file_mtime))
+  if (!(ctx.config.sloppiness().contains(core::Sloppy::include_file_mtime))
       && path_stat.mtime() >= ctx.time_of_compilation) {
     LOG("Include file {} too new", path);
     return true;
   }
 
   // The same >= logic as above applies to the change time of the file.
-  if (!(ctx.config.sloppiness().is_enabled(core::Sloppy::include_file_ctime))
+  if (!(ctx.config.sloppiness().contains(core::Sloppy::include_file_ctime))
       && path_stat.ctime() >= ctx.time_of_compilation) {
     LOG("Include file {} ctime too new", path);
     return true;
@@ -300,7 +318,7 @@ do_remember_include_file(Context& ctx,
   }
 
   if (system
-      && (ctx.config.sloppiness().is_enabled(core::Sloppy::system_headers))) {
+      && (ctx.config.sloppiness().contains(core::Sloppy::system_headers))) {
     // Don't remember this system header.
     return true;
   }
@@ -392,9 +410,9 @@ do_remember_include_file(Context& ctx,
 
   if (ctx.config.direct_mode()) {
     if (!is_pch) { // else: the file has already been hashed.
-      int result = hash_source_code_file(ctx, file_digest, path);
-      if (result & HASH_SOURCE_CODE_ERROR
-          || result & HASH_SOURCE_CODE_FOUND_TIME) {
+      auto ret = hash_source_code_file(ctx, file_digest, path);
+      if (ret.contains(HashSourceCode::error)
+          || ret.contains(HashSourceCode::found_time)) {
         return false;
       }
     }
@@ -457,6 +475,8 @@ process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path)
     return nonstd::make_unexpected(Statistic::internal_error);
   }
 
+  std::unordered_map<std::string, std::string> relative_inc_path_cache;
+
   // Bytes between p and q are pending to be hashed.
   char* q = &(*data)[0];
   const char* p = q;
@@ -559,10 +579,18 @@ process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path)
         }
         r++;
       }
+
       // p and q span the include file path.
       std::string inc_path(p, q - p);
-      inc_path = Util::normalize_concrete_absolute_path(inc_path);
-      inc_path = Util::make_relative_path(ctx, inc_path);
+      auto it = relative_inc_path_cache.find(inc_path);
+      if (it == relative_inc_path_cache.end()) {
+        auto rel_inc_path = Util::make_relative_path(
+          ctx, Util::normalize_concrete_absolute_path(inc_path));
+        relative_inc_path_cache.emplace(inc_path, rel_inc_path);
+        inc_path = std::move(rel_inc_path);
+      } else {
+        inc_path = it->second;
+      }
 
       if ((inc_path != ctx.apparent_cwd) || ctx.config.hash_dir()) {
         hash.hash(inc_path);
@@ -631,6 +659,11 @@ process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path)
 static std::optional<Digest>
 result_key_from_depfile(Context& ctx, Hash& hash)
 {
+  // Make sure that result hash will always be different from the manifest hash
+  // since there otherwise may a storage key collision (in case the dependency
+  // file is empty).
+  hash.hash_delimiter("result");
+
   const auto file_content =
     util::read_file<std::string>(ctx.args_info.output_dep);
   if (!file_content) {
@@ -699,9 +732,10 @@ struct DoExecuteResult
 static std::optional<Digest>
 result_key_from_includes(Context& ctx, Hash& hash, std::string_view stdout_data)
 {
-  for (std::string_view token : core::MsvcShowIncludesOutput::get_includes(
+  for (std::string_view include : core::MsvcShowIncludesOutput::get_includes(
          stdout_data, ctx.config.msvc_dep_prefix())) {
-    const std::string path = Util::make_relative_path(ctx, token);
+    const std::string path = Util::make_relative_path(
+      ctx, Util::normalize_abstract_absolute_path(include));
     remember_include_file(ctx, path, hash, false, &hash);
   }
 
@@ -815,7 +849,7 @@ update_manifest(Context& ctx,
   // so mtimes/ctimes are stored as a dummy value (-1) if not enabled. This
   // reduces the number of file_info entries for the common case.
   const bool save_timestamp =
-    (ctx.config.sloppiness().is_enabled(core::Sloppy::file_stat_matches))
+    (ctx.config.sloppiness().contains(core::Sloppy::file_stat_matches))
     || ctx.args_info.output_is_precompiled_header;
 
   const bool added = ctx.manifest.add_result(
@@ -995,7 +1029,7 @@ rewrite_stdout_from_compiler(const Context& ctx, util::Bytes&& stdout_data)
           ctx, Util::normalize_concrete_absolute_path(abs_inc_path));
         std::string line_with_rel_inc =
           util::replace_first(orig_line, abs_inc_path, rel_inc_path);
-        new_stdout_data.insert(new_stdout_data.begin(),
+        new_stdout_data.insert(new_stdout_data.end(),
                                line_with_rel_inc.data(),
                                line_with_rel_inc.size());
       } else {
@@ -1069,7 +1103,9 @@ to_cache(Context& ctx,
     // mode.
     Args depend_mode_args = ctx.orig_args;
     depend_mode_args.erase_with_prefix("--ccache-");
-    depend_mode_args.push_back(depend_extra_args);
+    // Add depend_mode_args directly after the compiler. We can't add them last
+    // since options then may be placed after a "--" option.
+    depend_mode_args.insert(1, depend_extra_args);
     add_prefix(ctx, depend_mode_args, ctx.config.prefix_command());
 
     ctx.time_of_compilation = util::TimePoint::now();
@@ -1403,7 +1439,7 @@ hash_common_info(const Context& ctx,
     }
   }
 
-  if (!(ctx.config.sloppiness().is_enabled(core::Sloppy::locale))) {
+  if (!(ctx.config.sloppiness().contains(core::Sloppy::locale))) {
     // Hash environment variables that may affect localization of compiler
     // warning messages.
     const char* envvars[] = {
@@ -1465,7 +1501,7 @@ hash_common_info(const Context& ctx,
   }
 
   if (ctx.args_info.generating_coverage
-      && !(ctx.config.sloppiness().is_enabled(core::Sloppy::gcno_cwd))) {
+      && !(ctx.config.sloppiness().contains(core::Sloppy::gcno_cwd))) {
     // GCC 9+ includes $PWD in the .gcno file. Since we don't have knowledge
     // about compiler version we always (unless sloppiness is wanted) include
     // the directory in the hash for now.
@@ -1643,7 +1679,7 @@ hash_argument(const Context& ctx,
   }
 
   if (util::starts_with(args[i], "-frandom-seed=")
-      && ctx.config.sloppiness().is_enabled(core::Sloppy::random_seed)) {
+      && ctx.config.sloppiness().contains(core::Sloppy::random_seed)) {
     LOG("Ignoring {} since random_seed sloppiness is requested", args[i]);
     return {};
   }
@@ -1804,12 +1840,12 @@ get_manifest_key(Context& ctx, Hash& hash)
 
   hash.hash_delimiter("sourcecode hash");
   Digest input_file_digest;
-  int result =
+  auto ret =
     hash_source_code_file(ctx, input_file_digest, ctx.args_info.input_file);
-  if (result & HASH_SOURCE_CODE_ERROR) {
+  if (ret.contains(HashSourceCode::error)) {
     return nonstd::make_unexpected(Statistic::internal_error);
   }
-  if (result & HASH_SOURCE_CODE_FOUND_TIME) {
+  if (ret.contains(HashSourceCode::found_time)) {
     LOG_RAW("Disabling direct mode");
     ctx.config.set_direct_mode(false);
     return {};
@@ -1937,10 +1973,10 @@ static nonstd::expected<std::pair<std::optional<Digest>, std::optional<Digest>>,
                         Failure>
 calculate_result_and_manifest_key(Context& ctx,
                                   const Args& args,
-                                  Args& preprocessor_args,
                                   Hash& hash,
-                                  bool direct_mode)
+                                  Args* preprocessor_args)
 {
+  bool direct_mode = !preprocessor_args;
   bool found_ccbin = false;
 
   hash.hash_delimiter("cache entry version");
@@ -1997,17 +2033,18 @@ calculate_result_and_manifest_key(Context& ctx,
       result_key = get_result_key_from_manifest(ctx, *manifest_key);
     }
   } else if (ctx.args_info.arch_args.empty()) {
-    const auto digest = get_result_key_from_cpp(ctx, preprocessor_args, hash);
+    const auto digest = get_result_key_from_cpp(ctx, *preprocessor_args, hash);
     if (!digest) {
       return nonstd::make_unexpected(digest.error());
     }
     result_key = *digest;
     LOG_RAW("Got result key from preprocessor");
   } else {
-    preprocessor_args.push_back("-arch");
+    preprocessor_args->push_back("-arch");
     for (size_t i = 0; i < ctx.args_info.arch_args.size(); ++i) {
-      preprocessor_args.push_back(ctx.args_info.arch_args[i]);
-      const auto digest = get_result_key_from_cpp(ctx, preprocessor_args, hash);
+      preprocessor_args->push_back(ctx.args_info.arch_args[i]);
+      const auto digest =
+        get_result_key_from_cpp(ctx, *preprocessor_args, hash);
       if (!digest) {
         return nonstd::make_unexpected(digest.error());
       }
@@ -2017,9 +2054,9 @@ calculate_result_and_manifest_key(Context& ctx,
       if (i != ctx.args_info.arch_args.size() - 1) {
         result_key = std::nullopt;
       }
-      preprocessor_args.pop_back();
+      preprocessor_args->pop_back();
     }
-    preprocessor_args.pop_back();
+    preprocessor_args->pop_back();
   }
 
   if (result_key) {
@@ -2034,8 +2071,6 @@ enum class FromCacheCallMode { direct, cpp };
 static nonstd::expected<bool, Failure>
 from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key)
 {
-  UmaskScope umask_scope(ctx.original_umask);
-
   // The user might be disabling cache hits.
   if (ctx.config.recache()) {
     return false;
@@ -2074,6 +2109,7 @@ from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key)
     cache_entry.verify_checksum();
     core::Result::Deserializer deserializer(cache_entry.payload());
     core::ResultRetriever result_retriever(ctx, result_key);
+    UmaskScope umask_scope(ctx.original_umask);
     deserializer.visit(result_retriever);
   } catch (core::ResultRetriever::WriteError& e) {
     LOG("Write error when retrieving result from {}: {}",
@@ -2093,25 +2129,18 @@ from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key)
 // PATH to find an executable of the same name that isn't ourselves.
 void
 find_compiler(Context& ctx,
-              const FindExecutableFunction& find_executable_function)
+              const FindExecutableFunction& find_executable_function,
+              bool masquerading_as_compiler)
 {
-  // gcc --> 0
-  // ccache gcc --> 1
-  // ccache ccache gcc --> 2
-  size_t compiler_pos = 0;
-  while (compiler_pos < ctx.orig_args.size()
-         && Util::is_ccache_executable(ctx.orig_args[compiler_pos])) {
-    ++compiler_pos;
-  }
-
   // Support user override of the compiler.
   const std::string compiler =
     !ctx.config.compiler().empty()
       ? ctx.config.compiler()
       // In case ccache is masquerading as the compiler, use only base_name so
       // the real compiler can be determined.
-      : (compiler_pos == 0 ? std::string(Util::base_name(ctx.orig_args[0]))
-                           : ctx.orig_args[compiler_pos]);
+      : (masquerading_as_compiler
+           ? std::string(Util::base_name(ctx.orig_args[0]))
+           : ctx.orig_args[0]);
 
   const std::string resolved_compiler =
     util::is_full_path(compiler)
@@ -2126,17 +2155,12 @@ find_compiler(Context& ctx,
     throw core::Fatal("Recursive invocation of ccache");
   }
 
-  ctx.orig_args.pop_front(compiler_pos);
   ctx.orig_args[0] = resolved_compiler;
 }
 
-// Initialize ccache. Must be called once before anything else is run.
 static void
-initialize(Context& ctx, int argc, const char* const* argv)
+initialize(Context& ctx, const char* const* argv, bool masquerading_as_compiler)
 {
-  ctx.orig_args = Args::from_argv(argc, argv);
-  ctx.storage.initialize();
-
   LOG("=== CCACHE {} STARTED =========================================",
       CCACHE_VERSION);
 
@@ -2150,6 +2174,42 @@ initialize(Context& ctx, int argc, const char* const* argv)
     LOG_RAW("Error: tracing is not enabled!");
 #endif
   }
+
+  if (!ctx.config.log_file().empty() || ctx.config.debug()) {
+    ctx.config.visit_items([&ctx](const std::string& key,
+                                  const std::string& value,
+                                  const std::string& origin) {
+      const auto& log_value =
+        key == "remote_storage"
+          ? ctx.storage.get_remote_storage_config_for_logging()
+          : value;
+      BULK_LOG("Config: ({}) {} = {}", origin, key, log_value);
+    });
+  }
+
+  LOG("Command line: {}", Util::format_argv_for_logging(argv));
+  LOG("Hostname: {}", Util::get_hostname());
+  LOG("Working directory: {}", ctx.actual_cwd);
+  if (ctx.apparent_cwd != ctx.actual_cwd) {
+    LOG("Apparent working directory: {}", ctx.apparent_cwd);
+  }
+
+  ctx.storage.initialize();
+
+  MTR_BEGIN("main", "find_compiler");
+  find_compiler(ctx, &find_executable, masquerading_as_compiler);
+  MTR_END("main", "find_compiler");
+
+  // Guess compiler after logging the config value in order to be able to
+  // display "compiler_type = auto" before overwriting the value with the
+  // guess.
+  if (ctx.config.compiler_type() == CompilerType::auto_guess) {
+    ctx.config.set_compiler_type(guess_compiler(ctx.orig_args[0]));
+  }
+  DEBUG_ASSERT(ctx.config.compiler_type() != CompilerType::auto_guess);
+
+  LOG("Compiler: {}", ctx.orig_args[0]);
+  LOG("Compiler type: {}", compiler_type_to_string(ctx.config.compiler_type()));
 }
 
 // Make a copy of stderr that will not be cached, so things like distcc can
@@ -2171,7 +2231,7 @@ set_up_uncached_err()
 static int cache_compilation(int argc, const char* const* argv);
 
 static nonstd::expected<core::StatisticsCounters, Failure>
-do_cache_compilation(Context& ctx, const char* const* argv);
+do_cache_compilation(Context& ctx);
 
 static void
 log_result_to_debug_log(Context& ctx)
@@ -2231,6 +2291,23 @@ finalize_at_exit(Context& ctx)
   }
 }
 
+ArgvParts
+split_argv(int argc, const char* const* argv)
+{
+  ArgvParts argv_parts;
+  int i = 0;
+  while (i < argc && Util::is_ccache_executable(argv[i])) {
+    argv_parts.masquerading_as_compiler = false;
+    ++i;
+  }
+  while (i < argc && std::strchr(argv[i], '=')) {
+    argv_parts.config_settings.emplace_back(argv[i]);
+    ++i;
+  }
+  argv_parts.compiler_and_args = Args::from_argv(argc - i, argv + i);
+  return argv_parts;
+}
+
 // The entry point when invoked to cache a compilation.
 static int
 cache_compilation(int argc, const char* const* argv)
@@ -2242,21 +2319,39 @@ cache_compilation(int argc, const char* const* argv)
   std::optional<uint32_t> original_umask;
   std::string saved_temp_dir;
 
+  auto argv_parts = split_argv(argc, argv);
+  if (argv_parts.compiler_and_args.empty()) {
+    throw core::Fatal("no compiler given, see \"ccache --help\"");
+  }
+
   {
     Context ctx;
-    ctx.initialize();
+    ctx.initialize(std::move(argv_parts.compiler_and_args),
+                   argv_parts.config_settings);
     SignalHandler signal_handler(ctx);
     Finalizer finalizer([&ctx] { finalize_at_exit(ctx); });
 
-    initialize(ctx, argc, argv);
+    initialize(ctx, argv, argv_parts.masquerading_as_compiler);
 
-    MTR_BEGIN("main", "find_compiler");
-    find_compiler(ctx, &find_executable);
-    MTR_END("main", "find_compiler");
+    const auto result = do_cache_compilation(ctx);
+    ctx.storage.local.increment_statistics(result ? *result
+                                                  : result.error().counters());
+    const auto& counters = ctx.storage.local.get_statistics_updates();
+
+    if (counters.get(Statistic::cache_miss) > 0) {
+      if (!ctx.config.remote_only()) {
+        ctx.storage.local.increment_statistic(Statistic::local_storage_miss);
+      }
+      if (ctx.storage.has_remote_storage()) {
+        ctx.storage.local.increment_statistic(Statistic::remote_storage_miss);
+      }
+    } else if ((counters.get(Statistic::direct_cache_hit) > 0
+                || counters.get(Statistic::preprocessed_cache_hit) > 0)
+               && counters.get(Statistic::remote_storage_hit) > 0
+               && !ctx.config.remote_only()) {
+      ctx.storage.local.increment_statistic(Statistic::local_storage_miss);
+    }
 
-    const auto result = do_cache_compilation(ctx, argv);
-    const auto& counters = result ? *result : result.error().counters();
-    ctx.storage.local.increment_statistics(counters);
     if (!result) {
       if (result.error().exit_code()) {
         return *result.error().exit_code();
@@ -2296,47 +2391,18 @@ cache_compilation(int argc, const char* const* argv)
 }
 
 static nonstd::expected<core::StatisticsCounters, Failure>
-do_cache_compilation(Context& ctx, const char* const* argv)
+do_cache_compilation(Context& ctx)
 {
-  if (ctx.actual_cwd.empty()) {
-    LOG("Unable to determine current working directory: {}", strerror(errno));
-    return nonstd::make_unexpected(Statistic::internal_error);
-  }
-
-  if (!ctx.config.log_file().empty() || ctx.config.debug()) {
-    ctx.config.visit_items([&ctx](const std::string& key,
-                                  const std::string& value,
-                                  const std::string& origin) {
-      const auto& log_value =
-        key == "remote_storage"
-          ? ctx.storage.get_remote_storage_config_for_logging()
-          : value;
-      BULK_LOG("Config: ({}) {} = {}", origin, key, log_value);
-    });
-  }
-
-  // Guess compiler after logging the config value in order to be able to
-  // display "compiler_type = auto" before overwriting the value with the
-  // guess.
-  if (ctx.config.compiler_type() == CompilerType::auto_guess) {
-    ctx.config.set_compiler_type(guess_compiler(ctx.orig_args[0]));
-  }
-  DEBUG_ASSERT(ctx.config.compiler_type() != CompilerType::auto_guess);
-
   if (ctx.config.disable()) {
     LOG_RAW("ccache is disabled");
     return nonstd::make_unexpected(Statistic::none);
   }
 
-  LOG("Command line: {}", Util::format_argv_for_logging(argv));
-  LOG("Hostname: {}", Util::get_hostname());
-  LOG("Working directory: {}", ctx.actual_cwd);
-  if (ctx.apparent_cwd != ctx.actual_cwd) {
-    LOG("Apparent working directory: {}", ctx.apparent_cwd);
+  if (ctx.actual_cwd.empty()) {
+    LOG("Unable to determine current working directory: {}", strerror(errno));
+    return nonstd::make_unexpected(Statistic::internal_error);
   }
 
-  LOG("Compiler type: {}", compiler_type_to_string(ctx.config.compiler_type()));
-
   // Set CCACHE_DISABLE so no process ccache executes from now on will risk
   // calling ccache second time. For instance, if the real compiler is a wrapper
   // script that calls "ccache $compiler ..." we want that inner ccache call to
@@ -2441,6 +2507,13 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   Hash common_hash;
   init_hash_debug(ctx, common_hash, 'c', "COMMON", debug_text_file);
 
+  if (should_disable_ccache_for_input_file(ctx.args_info.input_file)) {
+    LOG("{} found in {}, disabling ccache",
+        k_ccache_disable_token,
+        ctx.args_info.input_file);
+    return nonstd::make_unexpected(Statistic::disabled);
+  }
+
   {
     MTR_SCOPE("hash", "common_hash");
     TRY(hash_common_info(
@@ -2466,10 +2539,9 @@ do_cache_compilation(Context& ctx, const char* const* argv)
 
   if (ctx.config.direct_mode()) {
     LOG_RAW("Trying direct lookup");
-    Args dummy_args;
     MTR_BEGIN("hash", "direct_hash");
     const auto result_and_manifest_key = calculate_result_and_manifest_key(
-      ctx, args_to_hash, dummy_args, direct_hash, true);
+      ctx, args_to_hash, direct_hash, nullptr);
     MTR_END("hash", "direct_hash");
     if (!result_and_manifest_key) {
       return nonstd::make_unexpected(result_and_manifest_key.error());
@@ -2513,7 +2585,7 @@ do_cache_compilation(Context& ctx, const char* const* argv)
 
     MTR_BEGIN("hash", "cpp_hash");
     const auto result_and_manifest_key = calculate_result_and_manifest_key(
-      ctx, args_to_hash, processed.preprocessor_args, cpp_hash, false);
+      ctx, args_to_hash, cpp_hash, &processed.preprocessor_args);
     MTR_END("hash", "cpp_hash");
     if (!result_and_manifest_key) {
       return nonstd::make_unexpected(result_and_manifest_key.error());
@@ -2521,7 +2593,7 @@ do_cache_compilation(Context& ctx, const char* const* argv)
     result_key = result_and_manifest_key->first;
 
     // calculate_result_and_manifest_key always returns a non-nullopt result_key
-    // if the last argument (direct_mode) is false.
+    // in preprocessor mode (non-nullptr last argument).
     ASSERT(result_key);
 
     if (result_key_from_manifest && result_key_from_manifest != result_key) {
index 510563a..a3d0be7 100644 (file)
@@ -1,5 +1,5 @@
 // Copyright (C) 2002-2007 Andrew Tridgell
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #pragma once
 
-#include "Config.hpp"
+#include <Args.hpp>
+#include <Config.hpp>
 
 #include <functional>
 #include <string>
 #include <string_view>
+#include <vector>
 
 class Context;
 
@@ -37,6 +39,16 @@ using FindExecutableFunction =
 int ccache_main(int argc, const char* const* argv);
 
 // Tested by unit tests.
+struct ArgvParts
+{
+  bool masquerading_as_compiler = true;
+  std::vector<std::string> config_settings;
+  Args compiler_and_args;
+};
+
+ArgvParts split_argv(int argc, const char* const* argv);
+
 void find_compiler(Context& ctx,
-                   const FindExecutableFunction& find_executable_function);
+                   const FindExecutableFunction& find_executable_function,
+                   bool masquerading_as_compiler);
 CompilerType guess_compiler(std::string_view path);
index 867d8c4..668adcc 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -45,8 +45,6 @@
 // The option only affects compilation; not passed to the preprocessor.
 #define AFFECTS_COMP (1 << 6)
 
-#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
-
 struct CompOpt
 {
   const char* name;
@@ -54,9 +52,10 @@ struct CompOpt
 };
 
 const CompOpt compopts[] = {
-  {"--Werror", TAKES_ARG},                             // nvcc
+  {"--Werror", TAKES_ARG | AFFECTS_COMP},              // nvcc
   {"--analyze", TOO_HARD},                             // Clang
   {"--compiler-bindir", AFFECTS_CPP | TAKES_ARG},      // nvcc
+  {"--compiler-options", AFFECTS_CPP | TAKES_ARG},     // nvcc
   {"--config", TAKES_ARG},                             // Clang
   {"--gcc-toolchain=", TAKES_CONCAT_ARG | TAKES_PATH}, // Clang
   {"--libdevice-directory", AFFECTS_CPP | TAKES_ARG},  // nvcc
@@ -94,12 +93,11 @@ const CompOpt compopts[] = {
   {"-Wno-error", AFFECTS_COMP},
   {"-Xassembler", TAKES_ARG | TAKES_CONCAT_ARG | AFFECTS_COMP},
   {"-Xclang", TAKES_ARG},
+  {"-Xcompiler", AFFECTS_CPP | TAKES_ARG}, // nvcc
   {"-Xlinker", TAKES_ARG | TAKES_CONCAT_ARG | AFFECTS_COMP},
   {"-Xpreprocessor", AFFECTS_CPP | TOO_HARD_DIRECT | TAKES_ARG},
   {"-Yc", TAKES_ARG | TOO_HARD},                                    // msvc
   {"-Yu", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
-  {"-ZI", TOO_HARD},                                                // msvc
-  {"-Zi", TOO_HARD},                                                // msvc
   {"-all_load", AFFECTS_COMP},
   {"-analyze", TOO_HARD}, // Clang
   {"-arch", TAKES_ARG},
@@ -155,6 +153,7 @@ const CompOpt compopts[] = {
   {"-stdlib=", AFFECTS_CPP | TAKES_CONCAT_ARG},
   {"-trigraphs", AFFECTS_CPP},
   {"-u", TAKES_ARG | TAKES_CONCAT_ARG},
+  {"-v", AFFECTS_COMP},
   {"-z", TAKES_ARG | TAKES_CONCAT_ARG | AFFECTS_COMP},
 };
 
@@ -179,11 +178,8 @@ find(const std::string& option)
 {
   CompOpt key;
   key.name = option.c_str();
-  void* result = bsearch(&key,
-                         compopts,
-                         ARRAY_SIZE(compopts),
-                         sizeof(compopts[0]),
-                         compare_compopts);
+  void* result = bsearch(
+    &key, compopts, std::size(compopts), sizeof(compopts[0]), compare_compopts);
   return static_cast<CompOpt*>(result);
 }
 
@@ -194,7 +190,7 @@ find_prefix(const std::string& option)
   key.name = option.c_str();
   void* result = bsearch(&key,
                          compopts,
-                         ARRAY_SIZE(compopts),
+                         std::size(compopts),
                          sizeof(compopts[0]),
                          compare_prefix_compopts);
   return static_cast<CompOpt*>(result);
@@ -207,7 +203,7 @@ bool compopt_verify_sortedness_and_flags();
 bool
 compopt_verify_sortedness_and_flags()
 {
-  for (size_t i = 0; i < ARRAY_SIZE(compopts); i++) {
+  for (size_t i = 0; i < std::size(compopts); ++i) {
     if (compopts[i].type & TOO_HARD && compopts[i].type & TAKES_CONCAT_ARG) {
       PRINT(stderr,
             "type (TOO_HARD | TAKES_CONCAT_ARG) not allowed, used by {}\n",
index 0a448b9..6eee59c 100644 (file)
@@ -1,6 +1,7 @@
 set(
   sources
   CacheEntry.cpp
+  FileRecompressor.cpp
   Manifest.cpp
   MsvcShowIncludesOutput.cpp
   Result.cpp
index 2f4730e..fb1c1e3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -80,10 +80,10 @@ namespace core {
 const uint8_t CacheEntry::k_format_version = 1;
 
 CacheEntry::Header::Header(const Config& config,
-                           core::CacheEntryType entry_type)
+                           core::CacheEntryType entry_type_)
   : magic(k_ccache_magic),
     entry_format_version(k_format_version),
-    entry_type(entry_type),
+    entry_type(entry_type_),
     compression_type(compression_type_from_config(config)),
     compression_level(compression_level_from_config(config)),
     self_contained(entry_type != CacheEntryType::result
diff --git a/src/core/FileRecompressor.cpp b/src/core/FileRecompressor.cpp
new file mode 100644 (file)
index 0000000..92aa6c7
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "FileRecompressor.hpp"
+
+#include <AtomicFile.hpp>
+#include <Util.hpp>
+#include <core/CacheEntry.hpp>
+#include <core/exceptions.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+
+namespace core {
+
+Stat
+FileRecompressor::recompress(const Stat& stat,
+                             std::optional<int8_t> level,
+                             KeepAtime keep_atime)
+{
+  core::CacheEntry::Header header(stat.path());
+
+  const int8_t wanted_level =
+    level ? (*level == 0 ? core::CacheEntry::default_compression_level : *level)
+          : 0;
+
+  std::optional<Stat> new_stat;
+
+  if (header.compression_level != wanted_level) {
+    const auto cache_file_data = util::value_or_throw<core::Error>(
+      util::read_file<util::Bytes>(stat.path()),
+      FMT("Failed to read {}: ", stat.path()));
+    core::CacheEntry cache_entry(cache_file_data);
+    cache_entry.verify_checksum();
+
+    header.entry_format_version = core::CacheEntry::k_format_version;
+    header.compression_type =
+      level ? core::CompressionType::zstd : core::CompressionType::none;
+    header.compression_level = wanted_level;
+
+    AtomicFile new_cache_file(stat.path(), AtomicFile::Mode::binary);
+    new_cache_file.write(
+      core::CacheEntry::serialize(header, cache_entry.payload()));
+    new_cache_file.commit();
+    new_stat = Stat::lstat(stat.path(), Stat::OnError::log);
+  }
+
+  // Restore mtime/atime to keep cache LRU cleanup working as expected:
+  if (keep_atime == KeepAtime::yes || new_stat) {
+    util::set_timestamps(stat.path(), stat.mtime(), stat.atime());
+  }
+
+  m_content_size += util::likely_size_on_disk(header.entry_size);
+  m_old_size += stat.size_on_disk();
+  m_new_size += (new_stat ? *new_stat : stat).size_on_disk();
+
+  return new_stat ? *new_stat : stat;
+}
+
+uint64_t
+FileRecompressor::content_size() const
+{
+  return m_content_size;
+}
+
+uint64_t
+FileRecompressor::old_size() const
+{
+  return m_old_size;
+}
+
+uint64_t
+FileRecompressor::new_size() const
+{
+  return m_new_size;
+}
+
+} // namespace core
similarity index 58%
rename from src/storage/local/CacheFile.hpp
rename to src/core/FileRecompressor.hpp
index e653363..84ce282 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #include <Stat.hpp>
 
+#include <atomic>
+#include <cstdint>
+#include <mutex>
 #include <optional>
 #include <string>
 
-class CacheFile
+namespace core {
+
+class FileRecompressor
 {
 public:
-  enum class Type { result, manifest, raw, unknown };
+  enum class KeepAtime { yes, no };
+
+  FileRecompressor() = default;
 
-  explicit CacheFile(const std::string& path);
+  // Returns stat after recompression.
+  Stat recompress(const Stat& stat,
+                  std::optional<int8_t> level,
+                  KeepAtime keep_atime);
 
-  const Stat& lstat() const;
-  const std::string& path() const;
-  Type type() const;
+  uint64_t content_size() const;
+  uint64_t old_size() const;
+  uint64_t new_size() const;
 
 private:
-  std::string m_path;
-  mutable std::optional<Stat> m_stat;
+  std::atomic<uint64_t> m_content_size = 0;
+  std::atomic<uint64_t> m_old_size = 0;
+  std::atomic<uint64_t> m_new_size = 0;
 };
 
-inline CacheFile::CacheFile(const std::string& path) : m_path(path)
-{
-}
-
-inline const std::string&
-CacheFile::path() const
-{
-  return m_path;
-}
+} // namespace core
index 19093c0..c1c3e0c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -66,9 +66,9 @@ template<> struct hash<core::Manifest::FileInfo>
   operator()(const core::Manifest::FileInfo& file_info) const
   {
     static_assert(sizeof(file_info) == 48); // No padding.
-    util::XXH3_64 hash;
-    hash.update(&file_info, sizeof(file_info));
-    return hash.digest();
+    util::XXH3_64 h;
+    h.update(&file_info, sizeof(file_info));
+    return h.digest();
   }
 };
 
@@ -378,9 +378,9 @@ Manifest::result_matches(
       return false;
     }
 
-    if (ctx.config.sloppiness().is_enabled(core::Sloppy::file_stat_matches)) {
-      if (!(ctx.config.sloppiness().is_enabled(
-            core::Sloppy::file_stat_matches_ctime))) {
+    if (ctx.config.sloppiness().contains(core::Sloppy::file_stat_matches)) {
+      if (!ctx.config.sloppiness().contains(
+            core::Sloppy::file_stat_matches_ctime)) {
         if (fi.mtime == fs.mtime && fi.ctime == fs.ctime) {
           LOG("mtime/ctime hit for {}", path);
           continue;
@@ -400,12 +400,12 @@ Manifest::result_matches(
     auto hashed_files_iter = hashed_files.find(path);
     if (hashed_files_iter == hashed_files.end()) {
       Digest actual_digest;
-      int ret = hash_source_code_file(ctx, actual_digest, path, fs.size);
-      if (ret & HASH_SOURCE_CODE_ERROR) {
+      auto ret = hash_source_code_file(ctx, actual_digest, path, fs.size);
+      if (ret.contains(HashSourceCode::error)) {
         LOG("Failed hashing {}", path);
         return false;
       }
-      if (ret & HASH_SOURCE_CODE_FOUND_TIME) {
+      if (ret.contains(HashSourceCode::found_time)) {
         return false;
       }
 
index 0aecc17..0a67cdc 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -201,8 +201,8 @@ ResultRetriever::write_dependency_file(const std::string& path,
     throw WriteError(FMT("Failed to open {} for writing", path));
   }
 
-  auto write_data = [&](auto data, auto size) {
-    util::throw_on_error<WriteError>(util::write_fd(*fd, data, size),
+  auto write_data = [&](auto d, auto s) {
+    util::throw_on_error<WriteError>(util::write_fd(*fd, d, s),
                                      FMT("Failed to write to {}: ", path));
   };
 
index ef45907..2466934 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include <util/BitSet.hpp>
+
 #include <cstdint>
 #include <string>
 
@@ -53,48 +55,6 @@ enum class Sloppy : uint32_t {
   random_seed = 1U << 12,
 };
 
-class Sloppiness
-{
-public:
-  Sloppiness(Sloppy value = Sloppy::none);
-  explicit Sloppiness(uint32_t value);
-
-  void enable(Sloppy value);
-  bool is_enabled(Sloppy value) const;
-  uint32_t to_bitmask() const;
-
-private:
-  Sloppy m_sloppiness = Sloppy::none;
-};
-
-// --- Inline implementations ---
-
-inline Sloppiness::Sloppiness(Sloppy value) : m_sloppiness(value)
-{
-}
-
-inline Sloppiness::Sloppiness(uint32_t value)
-  : m_sloppiness(static_cast<Sloppy>(value))
-{
-}
-
-inline void
-Sloppiness::enable(Sloppy value)
-{
-  m_sloppiness = static_cast<Sloppy>(static_cast<uint32_t>(m_sloppiness)
-                                     | static_cast<uint32_t>(value));
-}
-
-inline bool
-Sloppiness::is_enabled(Sloppy value) const
-{
-  return static_cast<uint32_t>(m_sloppiness) & static_cast<uint32_t>(value);
-}
-
-inline uint32_t
-Sloppiness::to_bitmask() const
-{
-  return static_cast<uint32_t>(m_sloppiness);
-}
+using Sloppiness = util::BitSet<Sloppy>;
 
 } // namespace core
index 1f4fb92..fb0e379 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -72,7 +72,14 @@ enum class Statistic {
   remote_storage_hit = 47,
   remote_storage_miss = 48,
 
-  END
+  // 49-64: files in level 2 subdirs 0-f
+  subdir_files_base = 49,
+
+  // 65-80: size (KiB) in level 2 subdirs 0-f
+  subdir_size_kibibyte_base = 65,
+
+  disabled = 81,
+  END = 82
 };
 
 } // namespace core
index 662b59b..f3b1d72 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -66,71 +66,196 @@ struct StatisticsField
 
 const StatisticsField k_statistics_fields[] = {
   // Field "none" intentionally omitted.
+
+  // Uncacheable compilation or linking by an Autoconf test.
   FIELD(autoconf_test, "Autoconf compile/link", FLAG_UNCACHEABLE),
+
+  // Malformed compiler argument, e.g. missing a value for a compiler option
+  // that requires an argument or failure to read a file specified by a compiler
+  // option argument.
   FIELD(bad_compiler_arguments, "Bad compiler arguments", FLAG_UNCACHEABLE),
+
+  // The output path specified with -o could not be written to.
   FIELD(bad_output_file, "Could not write to output file", FLAG_ERROR),
+
+  // A cacheable call resulted in a miss.
   FIELD(cache_miss, nullptr),
+
+  // Size in KiB of a subdirectory of the cache. This is only set for level 1
+  // subdirectories.
   FIELD(cache_size_kibibyte, nullptr, FLAG_NOZERO),
+
+  // The compiler was called for linking, not compiling. Ccache only supports
+  // compilation of a single file, i.e. calling the compiler with the -c option
+  // to produce a single object file from a single source file.
   FIELD(called_for_link, "Called for linking", FLAG_UNCACHEABLE),
+
+  // The compiler was called for preprocessing, not compiling.
   FIELD(called_for_preprocessing, "Called for preprocessing", FLAG_UNCACHEABLE),
+
+  // How many cleanups were performed, either manually or automatically. Only
+  // cleanup operations that actually removed files are counted.
   FIELD(cleanups_performed, nullptr),
+
+  // The compilation failed. No result stored in the cache.
   FIELD(compile_failed, "Compilation failed", FLAG_UNCACHEABLE),
+
+  // A compiler check program specified by compiler_check/CCACHE_COMPILERCHECK
+  // failed.
   FIELD(compiler_check_failed, "Compiler check failed", FLAG_ERROR),
+
+  // One of the files expected to be produced by the compiler was missing after
+  // compilation.
   FIELD(compiler_produced_no_output,
         "Compiler output file missing",
         FLAG_UNCACHEABLE),
+
+  // The compiler's output file (typically an object file) was empty after
+  // compilation.
   FIELD(compiler_produced_empty_output,
         "Compiler produced empty output",
         FLAG_UNCACHEABLE),
+
+  // Compiler produced output. [This field is obsolete since ccache now supports
+  // caching stdout output as well.]
   FIELD(compiler_produced_stdout, "Compiler produced stdout", FLAG_UNCACHEABLE),
+
+  // The compiler to execute could not be found.
   FIELD(could_not_find_compiler, "Could not find compiler", FLAG_ERROR),
+
+  // Preconditions for using C++ modules were not fulfilled.
   FIELD(could_not_use_modules, "Could not use modules", FLAG_UNCACHEABLE),
+
+  // Preconditions for using precompiled headers were not fulfilled.
   FIELD(could_not_use_precompiled_header,
         "Could not use precompiled header",
         FLAG_UNCACHEABLE),
+
+  // A cacheable call resulted in a hit when attempting direct mode lookup.
   FIELD(direct_cache_hit, nullptr),
+
+  // A cacheable call resulted in a miss when attempting direct mode lookup.
   FIELD(direct_cache_miss, nullptr),
+
+  // Ccache was disabled by a ccache:disable string in the source code file.
+  FIELD(disabled, "Ccache disabled", FLAG_UNCACHEABLE),
+
+  // Failure reading a file specified by extra_files_to_hash/CCACHE_EXTRAFILES.
   FIELD(error_hashing_extra_file, "Error hashing extra file", FLAG_ERROR),
+
+  // Number of files in a subdirectory of the cache. This is only set for level
+  // 1 subdirectories.
   FIELD(files_in_cache, nullptr, FLAG_NOZERO),
+
+  // Unexpected failure, e.g. due to problems reading/writing the cache.
   FIELD(internal_error, "Internal error", FLAG_ERROR),
+
+  // A cacheable call resulted in a hit when attempting to look up a result from
+  // local storage.
   FIELD(local_storage_hit, nullptr),
+
+  // A cacheable call resulted in a miss when attempting to look up a result
+  // from local storage.
   FIELD(local_storage_miss, nullptr),
+
+  // A read from local storage found an entry (manifest or result file).
   FIELD(local_storage_read_hit, nullptr),
+
+  // A read from local storage did not find an entry (manifest or result file).
   FIELD(local_storage_read_miss, nullptr),
+
+  // An entry (manifest or result file) was written local storage.
   FIELD(local_storage_write, nullptr),
+
+  // A file was unexpectedly missing from the cache. This only happens in rare
+  // situations, e.g. if one ccache instance is about to get a file from the
+  // cache while another instance removed the file as part of cache cleanup.
   FIELD(missing_cache_file, "Missing cache file", FLAG_ERROR),
+
+  // The compiler was called to compile multiple source files in one go. This is
+  // not supported by ccache.
   FIELD(multiple_source_files, "Multiple source files", FLAG_UNCACHEABLE),
+
+  // No input file was specified to the compiler.
   FIELD(no_input_file, "No input file", FLAG_UNCACHEABLE),
+
+  // [Obsolete field used before ccache 3.2.]
   FIELD(obsolete_max_files, nullptr, FLAG_NOZERO | FLAG_NEVER),
+
+  // [Obsolete field used before ccache 3.2.]
   FIELD(obsolete_max_size, nullptr, FLAG_NOZERO | FLAG_NEVER),
+
+  // The compiler was instructed to write its output to standard output using
+  // "-o -". This is not supported by ccache.
   FIELD(output_to_stdout, "Output to stdout", FLAG_UNCACHEABLE),
+
+  // A cacheable call resulted in a hit when attempting preprocessed mode
+  // lookup.
   FIELD(preprocessed_cache_hit, nullptr),
+
+  // A cacheable call resulted in a miss when attempting preprocessed mode
+  // lookup.
   FIELD(preprocessed_cache_miss, nullptr),
+
+  // Preprocessing the source code using the compiler's -E option failed.
   FIELD(preprocessor_error, "Preprocessing failed", FLAG_UNCACHEABLE),
+
+  // recache/CCACHE_RECACHE was used to overwrite an existing result.
   FIELD(recache, "Forced recache", FLAG_UNCACHEABLE),
+
+  // Error when connecting to, reading from or writing to remote storage.
   FIELD(remote_storage_error, nullptr),
+
+  // A cacheable call resulted in a hit when attempting to look up a result from
+  // remote storage.
   FIELD(remote_storage_hit, nullptr),
+
+  // A cacheable call resulted in a miss when attempting to look up a result
+  // from remote storage.
   FIELD(remote_storage_miss, nullptr),
+
+  // A read from remote storage found an entry (manifest or result file).
   FIELD(remote_storage_read_hit, nullptr),
+
+  // A read from remote storage did not find an entry (manifest or result file).
   FIELD(remote_storage_read_miss, nullptr),
+
+  // An entry (manifest or result file) was written remote storage.
   FIELD(remote_storage_write, nullptr),
+
+  // Timeout when connecting to, reading from or writing to remote storage.
   FIELD(remote_storage_timeout, nullptr),
+
+  // Last time statistics counters were zeroed.
   FIELD(stats_zeroed_timestamp, nullptr),
+
+  // Code like the assembler .inc bin (without the space) directive was found.
+  // This is not supported by ccache.
   FIELD(
     unsupported_code_directive, "Unsupported code directive", FLAG_UNCACHEABLE),
+
+  // A compiler option not supported by ccache was found.
   FIELD(unsupported_compiler_option,
         "Unsupported compiler option",
         FLAG_UNCACHEABLE),
+
+  // An environment variable not supported by ccache was set.
   FIELD(unsupported_environment_variable,
         "Unsupported environment variable",
         FLAG_UNCACHEABLE),
+
+  // A source language e.g. specified with -x was unsupported by ccache.
   FIELD(unsupported_source_language,
         "Unsupported source language",
         FLAG_UNCACHEABLE),
+
+  // subdir_files_base and subdir_size_kibibyte_base are intentionally omitted
+  // since they are not interesting to show.
 };
 
-static_assert(sizeof(k_statistics_fields) / sizeof(k_statistics_fields[0])
-              == static_cast<size_t>(Statistic::END) - 1);
+static_assert(std::size(k_statistics_fields)
+              == static_cast<size_t>(Statistic::END)
+                   - (/*none*/ 1 + /*subdir files*/ 16 + /*subdir size*/ 16));
 
 static std::string
 format_timestamp(const util::TimePoint& value)
@@ -152,10 +277,13 @@ percent(const uint64_t nominator, const uint64_t denominator)
 {
   if (denominator == 0) {
     return "";
-  } else if (nominator >= denominator) {
-    return FMT("({:5.1f}%)", (100.0 * nominator) / denominator);
+  }
+
+  std::string result = FMT("({:5.2f}%)", (100.0 * nominator) / denominator);
+  if (result.length() <= 8) {
+    return result;
   } else {
-    return FMT("({:5.2f}%)", (100.0 * nominator) / denominator);
+    return FMT("({:5.1f}%)", (100.0 * nominator) / denominator);
   }
 }
 
@@ -298,7 +426,13 @@ Statistics::format_human_readable(const Config& config,
     add_ratio_row(table, "  Preprocessed:", p_hits, p_hits + p_misses);
   }
 
-  const uint64_t g = 1'000'000'000;
+  const char* size_unit =
+    config.size_unit_prefix_type() == util::SizeUnitPrefixType::binary ? "GiB"
+                                                                       : "GB";
+  const uint64_t size_divider =
+    config.size_unit_prefix_type() == util::SizeUnitPrefixType::binary
+      ? 1024 * 1024 * 1024
+      : 1000 * 1000 * 1000;
   const uint64_t local_hits = S(local_storage_hit);
   const uint64_t local_misses = S(local_storage_miss);
   const uint64_t local_reads =
@@ -314,21 +448,24 @@ Statistics::format_human_readable(const Config& config,
   const uint64_t remote_errors = S(remote_storage_error);
   const uint64_t remote_timeouts = S(remote_storage_timeout);
 
-  table.add_heading("Local storage:");
+  if (!from_log || verbosity > 0 || (local_hits + local_misses) > 0) {
+    table.add_heading("Local storage:");
+  }
   if (!from_log) {
     std::vector<C> size_cells{
-      "  Cache size (GB):",
-      C(FMT("{:.2f}", static_cast<double>(local_size) / g)).right_align()};
+      FMT("  Cache size ({}):", size_unit),
+      C(FMT("{:.1f}", static_cast<double>(local_size) / size_divider))
+        .right_align()};
     if (config.max_size() != 0) {
       size_cells.emplace_back("/");
       size_cells.emplace_back(
-        C(FMT("{:.2f}", static_cast<double>(config.max_size()) / g))
+        C(FMT("{:.1f}", static_cast<double>(config.max_size()) / size_divider))
           .right_align());
       size_cells.emplace_back(percent(local_size, config.max_size()));
     }
     table.add_row(size_cells);
 
-    if (verbosity > 0) {
+    if (verbosity > 0 || config.max_files() > 0) {
       std::vector<C> files_cells{"  Files:", S(files_in_cache)};
       if (config.max_files() > 0) {
         files_cells.emplace_back("/");
@@ -342,7 +479,7 @@ Statistics::format_human_readable(const Config& config,
       table.add_row({"  Cleanups:", cleanups});
     }
   }
-  if (verbosity > 0 || (remote_hits + remote_misses) > 0) {
+  if (verbosity > 0 || (local_hits + local_misses) > 0) {
     add_ratio_row(table, "  Hits:", local_hits, local_hits + local_misses);
     add_ratio_row(table, "  Misses:", local_misses, local_hits + local_misses);
   }
index 3f1f2d6..f036171 100644 (file)
@@ -51,6 +51,19 @@ StatisticsCounters::get(Statistic statistic) const
   return index < m_counters.size() ? m_counters[index] : 0;
 }
 
+uint64_t
+StatisticsCounters::get_offsetted(Statistic statistic, size_t offset) const
+{
+  return get_raw(static_cast<size_t>(statistic) + offset);
+}
+
+uint64_t
+StatisticsCounters::get_raw(size_t index) const
+{
+  ASSERT(index < size());
+  return m_counters[index];
+}
+
 void
 StatisticsCounters::set(Statistic statistic, uint64_t value)
 {
@@ -59,11 +72,12 @@ StatisticsCounters::set(Statistic statistic, uint64_t value)
   m_counters[index] = value;
 }
 
-uint64_t
-StatisticsCounters::get_raw(size_t index) const
+void
+StatisticsCounters::set_offsetted(Statistic statistic,
+                                  size_t offset,
+                                  uint64_t value)
 {
-  ASSERT(index < size());
-  return m_counters[index];
+  set_raw(static_cast<size_t>(statistic) + offset, value);
 }
 
 void
@@ -101,6 +115,15 @@ StatisticsCounters::increment(const StatisticsCounters& other)
   }
 }
 
+void
+StatisticsCounters::increment_offsetted(Statistic statistic,
+                                        size_t offset,
+                                        int64_t value)
+{
+  increment(static_cast<Statistic>(static_cast<size_t>(statistic) + offset),
+            value);
+}
+
 size_t
 StatisticsCounters::size() const
 {
index 21bb1b3..d886073 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -37,21 +37,39 @@ public:
   StatisticsCounters(std::initializer_list<Statistic> statistics);
 
   uint64_t get(Statistic statistic) const;
-  void set(Statistic statistic, uint64_t value);
-
+  uint64_t get_offsetted(Statistic statistic, size_t offset) const;
   uint64_t get_raw(size_t index) const;
+
+  void set(Statistic statistic, uint64_t value);
+  void set_offsetted(Statistic statistic, size_t offset, uint64_t value);
   void set_raw(size_t index, uint64_t value);
 
   void increment(Statistic statistic, int64_t value = 1);
   void increment(const StatisticsCounters& other);
+  void increment_offsetted(Statistic statistic, size_t offset, int64_t value);
 
   size_t size() const;
 
   // Return true if all counters are zero, false otherwise.
   bool all_zero() const;
 
+  bool operator==(const StatisticsCounters& other) const noexcept;
+  bool operator!=(const StatisticsCounters& other) const noexcept;
+
 private:
   std::vector<uint64_t> m_counters;
 };
 
+inline bool
+StatisticsCounters::operator==(const StatisticsCounters& other) const noexcept
+{
+  return m_counters == other.m_counters;
+}
+
+inline bool
+StatisticsCounters::operator!=(const StatisticsCounters& other) const noexcept
+{
+  return !(*this == other);
+}
+
 } // namespace core
index 2283162..2789876 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 #include <File.hpp>
 #include <Hash.hpp>
 #include <InodeCache.hpp>
+#include <Logging.hpp>
 #include <ProgressBar.hpp>
+#include <TemporaryFile.hpp>
+#include <ThreadPool.hpp>
 #include <UmaskScope.hpp>
+#include <assertions.hpp>
 #include <ccache.hpp>
 #include <core/CacheEntry.hpp>
+#include <core/FileRecompressor.hpp>
 #include <core/Manifest.hpp>
 #include <core/Result.hpp>
 #include <core/ResultExtractor.hpp>
 #include <fcntl.h>
 
 #include <algorithm>
+#include <atomic>
 #include <optional>
 #include <string>
+#include <thread>
 
 #ifdef HAVE_UNISTD_H
 #  include <unistd.h>
@@ -70,7 +77,7 @@ constexpr const char VERSION_TEXT[] =
 Features: {2}
 
 Copyright (C) 2002-2007 Andrew Tridgell
-Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 
 See <https://ccache.dev/credits.html> for a complete list of contributors.
 
@@ -82,14 +89,18 @@ version.
 
 constexpr const char USAGE_TEXT[] =
   R"(Usage:
-    {0} [options]
-    {0} compiler [compiler options]
-    compiler [compiler options]            (ccache masquerading as the compiler)
+    {0} [ccache options]
+    {0} [KEY=VALUE ...] compiler [compiler options]
+    compiler [compiler options]
+
+    The first form takes options described below. The second form invokes the
+    compiler, optionally using configuration options from KEY=VALUE arguments.
+    In the third form, ccache is masquerading as the compiler.
 
 Common options:
-    -c, --cleanup              delete old files and recalculate size counters
-                               (normally not needed as this is done
-                               automatically)
+    -c, --cleanup              delete not recently used files and recalculate
+                               size counters (normally not needed as this is
+                               done automatically)
     -C, --clear                clear the cache completely (except configuration)
         --config-path PATH     operate on configuration file PATH instead of the
                                default
@@ -97,17 +108,21 @@ Common options:
                                default
         --evict-namespace NAMESPACE
                                remove files created in namespace NAMESPACE
-        --evict-older-than AGE remove files older than AGE (unsigned integer
-                               with a d (days) or s (seconds) suffix)
+        --evict-older-than AGE remove files used less recently than AGE
+                               (unsigned integer with a d (days) or s (seconds)
+                               suffix)
     -F, --max-files NUM        set maximum number of files in cache to NUM (use
                                0 for no limit)
     -M, --max-size SIZE        set maximum size of cache to SIZE (use 0 for no
-                               limit); available suffixes: k, M, G, T (decimal)
-                               and Ki, Mi, Gi, Ti (binary); default suffix: G
+                               limit); available suffixes: kB, MB, GB, TB
+                               (decimal) and KiB, MiB, GiB, TiB (binary);
+                               default suffix: GiB
     -X, --recompress LEVEL     recompress the cache to level LEVEL (integer or
-                               "uncompressed") using the Zstandard algorithm;
-                               see "Cache compression" in the manual for details
-    -o, --set-config KEY=VAL   set configuration item KEY to value VAL
+                               "uncompressed")
+        --recompress-threads THREADS
+                               use up to THREADS threads when recompressing the
+                               cache; default: number of CPUs
+    -o, --set-config KEY=VALUE set configuration option KEY to value VALUE
     -x, --show-compression     show compression statistics
     -p, --show-config          show current configuration options in
                                human-readable format
@@ -123,15 +138,22 @@ Common options:
     -V, --version              print version and copyright information
 
 Options for remote file-based storage:
-        --trim-dir PATH        remove old files from directory PATH until it is
-                               at most the size specified by --trim-max-size
-                               (note: don't use this option to trim the local
-                               cache)
-        --trim-max-size SIZE   specify the maximum size for --trim-dir;
-                               available suffixes: k, M, G, T (decimal) and Ki,
-                               Mi, Gi, Ti (binary); default suffix: G
+        --trim-dir PATH        remove not recently used files from directory
+                               PATH until it is at most the size specified by
+                               --trim-max-size (note: don't use this option to
+                               trim the local cache)
+        --trim-max-size SIZE   specify the maximum size for --trim-dir (use 0 for
+                               no limit); available suffixes: kB, MB, GB, TB
+                               (decimal) and KiB, MiB, GiB, TiB (binary);
+                               default suffix: GiB
         --trim-method METHOD   specify the method (atime or mtime) for
                                --trim-dir; default: atime
+        --trim-recompress LEVEL
+                               recompress to level LEVEL (integer or
+                               "uncompressed")
+        --trim-recompress-threads THREADS
+                               use up to THREADS threads when recompressing;
+                               default: number of CPUs
 
 Options for scripting or debugging:
         --checksum-file PATH   print the checksum (128 bit XXH3) of the file at
@@ -215,39 +237,61 @@ inspect_path(const std::string& path)
 }
 
 static void
-print_compression_statistics(const storage::local::CompressionStatistics& cs)
+print_compression_statistics(const Config& config,
+                             const storage::local::CompressionStatistics& cs)
 {
-  const double ratio = cs.compr_size > 0
-                         ? static_cast<double>(cs.content_size) / cs.compr_size
+  const double ratio = cs.actual_size > 0
+                         ? static_cast<double>(cs.content_size) / cs.actual_size
                          : 0.0;
   const double savings = ratio > 0.0 ? 100.0 - (100.0 / ratio) : 0.0;
 
+  auto human_readable = [&](uint64_t size) {
+    return util::format_human_readable_size(size,
+                                            config.size_unit_prefix_type());
+  };
+
+  const auto [total_data_quantity, total_data_unit] = util::split_once(
+    human_readable(cs.actual_size + cs.incompressible_size), ' ');
+  ASSERT(total_data_unit);
+  const auto [compressed_data_quantity, compressed_data_unit] =
+    util::split_once(human_readable(cs.actual_size), ' ');
+  ASSERT(compressed_data_unit);
+  const auto [original_data_quantity, original_data_unit] =
+    util::split_once(human_readable(cs.content_size), ' ');
+  ASSERT(original_data_unit);
+  const auto [incompressible_data_quantity, incompressible_data_unit] =
+    util::split_once(human_readable(cs.incompressible_size), ' ');
+  ASSERT(incompressible_data_unit);
+
   using C = util::TextTable::Cell;
-  auto human_readable = Util::format_human_readable_size;
   util::TextTable table;
 
   table.add_row({
     "Total data:",
-    C(human_readable(cs.compr_size + cs.incompr_size)).right_align(),
-    FMT("({} disk blocks)", human_readable(cs.on_disk_size)),
+    C(total_data_quantity).right_align(),
+    *total_data_unit,
   });
   table.add_row({
     "Compressed data:",
-    C(human_readable(cs.compr_size)).right_align(),
+    C(compressed_data_quantity).right_align(),
+    *compressed_data_unit,
     FMT("({:.1f}% of original size)", 100.0 - savings),
   });
   table.add_row({
     "  Original size:",
-    C(human_readable(cs.content_size)).right_align(),
+    C(original_data_quantity).right_align(),
+    *original_data_unit,
   });
   table.add_row({
     "  Compression ratio:",
-    C(FMT("{:.3f} x ", ratio)).right_align(),
+    C(FMT("{:.3f}", ratio)).right_align(),
+    "x",
     FMT("({:.1f}% space savings)", savings),
   });
   table.add_row({
     "Incompressible data:",
-    C(human_readable(cs.incompr_size)).right_align(),
+    C(incompressible_data_quantity).right_align(),
+    *incompressible_data_unit,
   });
 
   PRINT_RAW(stdout, table.render());
@@ -256,53 +300,105 @@ print_compression_statistics(const storage::local::CompressionStatistics& cs)
 static void
 trim_dir(const std::string& dir,
          const uint64_t trim_max_size,
-         const bool trim_lru_mtime)
+         const util::SizeUnitPrefixType suffix_type,
+         const bool trim_lru_mtime,
+         std::optional<std::optional<int8_t>> recompress_level,
+         uint32_t recompress_threads)
 {
-  struct File
-  {
-    std::string path;
-    Stat stat;
-  };
-  std::vector<File> files;
-  uint64_t size_before = 0;
+  std::vector<Stat> files;
+  uint64_t initial_size = 0;
 
   Util::traverse(dir, [&](const std::string& path, const bool is_dir) {
-    const auto stat = Stat::lstat(path);
+    if (is_dir || TemporaryFile::is_tmp_file(path)) {
+      return;
+    }
+    auto stat = Stat::lstat(path);
     if (!stat) {
       // Probably some race, ignore.
       return;
     }
-    size_before += stat.size_on_disk();
-    if (!is_dir) {
-      const auto name = Util::base_name(path);
-      if (name == "ccache.conf" || name == "stats") {
-        throw Fatal(
-          FMT("this looks like a local cache directory (found {})", path));
-      }
-      files.push_back({path, stat});
+    initial_size += stat.size_on_disk();
+    const auto name = Util::base_name(path);
+    if (name == "ccache.conf" || name == "stats") {
+      throw Fatal(
+        FMT("this looks like a local cache directory (found {})", path));
     }
+    files.emplace_back(std::move(stat));
   });
 
   std::sort(files.begin(), files.end(), [&](const auto& f1, const auto& f2) {
-    return trim_lru_mtime ? f1.stat.mtime() < f2.stat.mtime()
-                          : f1.stat.atime() < f2.stat.atime();
+    return trim_lru_mtime ? f1.mtime() < f2.mtime() : f1.atime() < f2.atime();
   });
 
-  uint64_t size_after = size_before;
+  int64_t recompression_diff = 0;
+
+  if (recompress_level) {
+    const size_t read_ahead = std::max(
+      static_cast<size_t>(10), 2 * static_cast<size_t>(recompress_threads));
+    ThreadPool thread_pool(recompress_threads, read_ahead);
+    core::FileRecompressor recompressor;
+
+    std::atomic<uint64_t> incompressible_size = 0;
+    for (auto& file : files) {
+      thread_pool.enqueue([&] {
+        try {
+          auto new_stat = recompressor.recompress(
+            file, *recompress_level, core::FileRecompressor::KeepAtime::yes);
+          file = std::move(new_stat); // Remember new size, if any.
+        } catch (core::Error&) {
+          // Ignore for now.
+          incompressible_size += file.size_on_disk();
+        }
+      });
+    }
 
-  for (const auto& file : files) {
-    if (size_after <= trim_max_size) {
-      break;
+    thread_pool.shut_down();
+    recompression_diff = recompressor.new_size() - recompressor.old_size();
+    PRINT(stdout,
+          "Recompressed {} to {} ({})\n",
+          util::format_human_readable_size(
+            incompressible_size + recompressor.old_size(), suffix_type),
+          util::format_human_readable_size(
+            incompressible_size + recompressor.new_size(), suffix_type),
+          util::format_human_readable_diff(recompression_diff, suffix_type));
+  }
+
+  uint64_t size_after_recompression = initial_size + recompression_diff;
+  uint64_t final_size = size_after_recompression;
+
+  size_t removed_files = 0;
+  if (trim_max_size > 0) {
+    for (const auto& file : files) {
+      if (final_size <= trim_max_size) {
+        break;
+      }
+      if (Util::unlink_tmp(file.path())) {
+        ++removed_files;
+        final_size -= file.size_on_disk();
+      }
     }
-    Util::unlink_tmp(file.path);
-    size_after -= file.stat.size();
   }
 
   PRINT(stdout,
-        "Removed {} ({} -> {})\n",
-        Util::format_human_readable_size(size_before - size_after),
-        Util::format_human_readable_size(size_before),
-        Util::format_human_readable_size(size_after));
+        "Trimmed {} to {} ({}, {}{} file{})\n",
+        util::format_human_readable_size(size_after_recompression, suffix_type),
+        util::format_human_readable_size(final_size, suffix_type),
+        util::format_human_readable_diff(final_size - size_after_recompression,
+                                         suffix_type),
+        removed_files == 0 ? "" : "-",
+        removed_files,
+        removed_files == 1 ? "" : "s");
+}
+
+std::optional<int8_t>
+parse_compression_level(std::string_view level)
+{
+  if (level == "uncompressed") {
+    return std::nullopt;
+  } else {
+    return util::value_or_throw<Error>(
+      util::parse_signed(level, INT8_MIN, INT8_MAX, "compression level"));
+  }
 }
 
 static std::string
@@ -329,10 +425,13 @@ enum {
   HASH_FILE,
   INSPECT,
   PRINT_STATS,
+  RECOMPRESS_THREADS,
   SHOW_LOG_STATS,
   TRIM_DIR,
   TRIM_MAX_SIZE,
   TRIM_METHOD,
+  TRIM_RECOMPRESS,
+  TRIM_RECOMPRESS_THREADS,
 };
 
 const char options_string[] = "cCd:k:hF:M:po:svVxX:z";
@@ -356,6 +455,7 @@ const option long_options[] = {
   {"max-size", required_argument, nullptr, 'M'},
   {"print-stats", no_argument, nullptr, PRINT_STATS},
   {"recompress", required_argument, nullptr, 'X'},
+  {"recompress-threads", required_argument, nullptr, RECOMPRESS_THREADS},
   {"set-config", required_argument, nullptr, 'o'},
   {"show-compression", no_argument, nullptr, 'x'},
   {"show-config", no_argument, nullptr, 'p'},
@@ -364,6 +464,11 @@ const option long_options[] = {
   {"trim-dir", required_argument, nullptr, TRIM_DIR},
   {"trim-max-size", required_argument, nullptr, TRIM_MAX_SIZE},
   {"trim-method", required_argument, nullptr, TRIM_METHOD},
+  {"trim-recompress", required_argument, nullptr, TRIM_RECOMPRESS},
+  {"trim-recompress-threads",
+   required_argument,
+   nullptr,
+   TRIM_RECOMPRESS_THREADS},
   {"verbose", no_argument, nullptr, 'v'},
   {"version", no_argument, nullptr, 'V'},
   {"zero-stats", no_argument, nullptr, 'z'},
@@ -373,12 +478,20 @@ int
 process_main_options(int argc, const char* const* argv)
 {
   int c;
+
+  uint8_t verbosity = 0;
+
   std::optional<uint64_t> trim_max_size;
+  std::optional<util::SizeUnitPrefixType> trim_suffix_type;
   bool trim_lru_mtime = false;
-  uint8_t verbosity = 0;
+  std::optional<std::optional<int8_t>> trim_recompress;
+  uint32_t trim_recompress_threads = std::thread::hardware_concurrency();
+
   std::optional<std::string> evict_namespace;
   std::optional<uint64_t> evict_max_age;
 
+  uint32_t recompress_threads = std::thread::hardware_concurrency();
+
   // First pass: Handle non-command options that affect command options.
   while ((c = getopt_long(argc,
                           const_cast<char* const*>(argv),
@@ -397,14 +510,33 @@ process_main_options(int argc, const char* const* argv)
       Util::setenv("CCACHE_CONFIGPATH", arg);
       break;
 
-    case TRIM_MAX_SIZE:
-      trim_max_size = Util::parse_size(arg);
+    case RECOMPRESS_THREADS:
+      recompress_threads = util::value_or_throw<Error>(util::parse_unsigned(
+        arg, 1, std::numeric_limits<uint32_t>::max(), "threads"));
       break;
 
+    case TRIM_MAX_SIZE: {
+      auto [size, suffix_type] =
+        util::value_or_throw<Error>(util::parse_size(arg));
+      trim_max_size = size;
+      trim_suffix_type = suffix_type;
+      break;
+    }
+
     case TRIM_METHOD:
       trim_lru_mtime = (arg == "ctime");
       break;
 
+    case TRIM_RECOMPRESS:
+      trim_recompress = parse_compression_level(arg);
+      break;
+
+    case TRIM_RECOMPRESS_THREADS:
+      trim_recompress_threads =
+        util::value_or_throw<Error>(util::parse_unsigned(
+          arg, 1, std::numeric_limits<uint32_t>::max(), "threads"));
+      break;
+
     case 'v': // --verbose
       ++verbosity;
       break;
@@ -424,6 +556,7 @@ process_main_options(int argc, const char* const* argv)
          != -1) {
     Config config;
     config.read();
+    Logging::init(config);
 
     UmaskScope umask_scope(config.umask());
 
@@ -432,8 +565,11 @@ process_main_options(int argc, const char* const* argv)
     switch (c) {
     case CONFIG_PATH:
     case 'd': // --dir
+    case RECOMPRESS_THREADS:
     case TRIM_MAX_SIZE:
     case TRIM_METHOD:
+    case TRIM_RECOMPRESS:
+    case TRIM_RECOMPRESS_THREADS:
     case 'v': // --verbose
       // Already handled in the first pass.
       break;
@@ -556,14 +692,16 @@ process_main_options(int argc, const char* const* argv)
     }
 
     case 'M': { // --max-size
-      uint64_t size = Util::parse_size(arg);
+      auto [size, suffix_type] =
+        util::value_or_throw<Error>(util::parse_size(arg));
+      uint64_t max_size = size;
       config.set_value_in_file(config.config_path(), "max_size", arg);
-      if (size == 0) {
+      if (max_size == 0) {
         PRINT_RAW(stdout, "Unset cache size limit\n");
       } else {
         PRINT(stdout,
               "Set cache size limit to {}\n",
-              Util::format_human_readable_size(size));
+              util::format_human_readable_size(max_size, suffix_type));
       }
       break;
     }
@@ -612,7 +750,12 @@ process_main_options(int argc, const char* const* argv)
       if (!trim_max_size) {
         throw Error("please specify --trim-max-size when using --trim-dir");
       }
-      trim_dir(arg, *trim_max_size, trim_lru_mtime);
+      trim_dir(arg,
+               *trim_max_size,
+               *trim_suffix_type,
+               trim_lru_mtime,
+               trim_recompress,
+               trim_recompress_threads);
       break;
 
     case 'V': // --version
@@ -634,23 +777,19 @@ process_main_options(int argc, const char* const* argv)
       if (isatty(STDOUT_FILENO)) {
         PRINT_RAW(stdout, "\n\n");
       }
-      print_compression_statistics(compression_statistics);
+      print_compression_statistics(config, compression_statistics);
       break;
     }
 
     case 'X': // --recompress
     {
-      std::optional<int8_t> wanted_level;
-      if (arg == "uncompressed") {
-        wanted_level = std::nullopt;
-      } else {
-        wanted_level = util::value_or_throw<Error>(
-          util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level"));
-      }
+      auto wanted_level = parse_compression_level(arg);
 
       ProgressBar progress_bar("Recompressing...");
       storage::local::LocalStorage(config).recompress(
-        wanted_level, [&](double progress) { progress_bar.update(progress); });
+        wanted_level, recompress_threads, [&](double progress) {
+          progress_bar.update(progress);
+        });
       break;
     }
 
index a217d65..de684fd 100644 (file)
@@ -1,5 +1,5 @@
 // Copyright (C) 2002 Andrew Tridgell
-// Copyright (C) 2011-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2011-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -58,6 +58,8 @@ static int win32execute(const char* path,
 int
 execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err)
 {
+  LOG("Executing {}", Util::format_argv_for_logging(argv));
+
   return win32execute(argv[0],
                       argv,
                       1,
index 8d7151d..1b019f9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 namespace {
 
-// Returns one of HASH_SOURCE_CODE_FOUND_DATE, HASH_SOURCE_CODE_FOUND_TIME or
-// HASH_SOURCE_CODE_FOUND_TIMESTAMP if "_DATE__", "_TIME__" or "_TIMESTAMP__"
-// starts at str[pos].
-//
 // Pre-condition: str[pos - 1] == '_'
-int
+HashSourceCode
 check_for_temporal_macros_helper(std::string_view str, size_t pos)
 {
   if (pos + 7 > str.length()) {
-    return 0;
+    return HashSourceCode::ok;
   }
 
-  int found = 0;
+  HashSourceCode found = HashSourceCode::ok;
   int macro_len = 7;
   if (memcmp(&str[pos], "_DATE__", 7) == 0) {
-    found = HASH_SOURCE_CODE_FOUND_DATE;
+    found = HashSourceCode::found_date;
   } else if (memcmp(&str[pos], "_TIME__", 7) == 0) {
-    found = HASH_SOURCE_CODE_FOUND_TIME;
+    found = HashSourceCode::found_time;
   } else if (pos + 12 <= str.length()
              && memcmp(&str[pos], "_TIMESTAMP__", 12) == 0) {
-    found = HASH_SOURCE_CODE_FOUND_TIMESTAMP;
+    found = HashSourceCode::found_timestamp;
     macro_len = 12;
   } else {
-    return 0;
+    return HashSourceCode::ok;
   }
 
   // Check char before and after macro to verify that the found macro isn't part
@@ -89,25 +85,25 @@ check_for_temporal_macros_helper(std::string_view str, size_t pos)
     return found;
   }
 
-  return 0;
+  return HashSourceCode::ok;
 }
 
-int
-check_for_temporal_macros_bmh(std::string_view str)
+HashSourceCodeResult
+check_for_temporal_macros_bmh(std::string_view str, size_t start = 0)
 {
-  int result = 0;
+  HashSourceCodeResult result;
 
   // We're using the Boyer-Moore-Horspool algorithm, which searches starting
   // from the *end* of the needle. Our needles are 8 characters long, so i
   // starts at 7.
-  size_t i = 7;
+  size_t i = start + 7;
 
   while (i < str.length()) {
     // Check whether the substring ending at str[i] has the form "_....E..". On
     // the assumption that 'E' is less common in source than '_', we check
     // str[i-2] first.
     if (str[i - 2] == 'E' && str[i - 7] == '_') {
-      result |= check_for_temporal_macros_helper(str, i - 6);
+      result.insert(check_for_temporal_macros_helper(str, i - 6));
     }
 
     // macro_skip tells us how far we can skip forward upon seeing str[i] at
@@ -120,17 +116,17 @@ check_for_temporal_macros_bmh(std::string_view str)
 
 #ifdef HAVE_AVX2
 #  ifndef _MSC_VER // MSVC does not need explicit enabling of AVX2.
-int check_for_temporal_macros_avx2(std::string_view str)
+HashSourceCodeResult check_for_temporal_macros_avx2(std::string_view str)
   __attribute__((target("avx2")));
 #  endif
 
 // The following algorithm, which uses AVX2 instructions to find __DATE__,
 // __TIME__ and __TIMESTAMP__, is heavily inspired by
 // <http://0x80.pl/articles/simd-strfind.html>.
-int
+HashSourceCodeResult
 check_for_temporal_macros_avx2(std::string_view str)
 {
-  int result = 0;
+  HashSourceCodeResult result;
 
   // Set all 32 bytes in first and last to '_' and 'E' respectively.
   const __m256i first = _mm256_set1_epi8('_');
@@ -169,17 +165,17 @@ check_for_temporal_macros_avx2(std::string_view str)
       // Clear the least significant bit set.
       mask = mask & (mask - 1);
 
-      result |= check_for_temporal_macros_helper(str, start);
+      result.insert(check_for_temporal_macros_helper(str, start));
     }
   }
 
-  result |= check_for_temporal_macros_bmh(str.substr(pos));
+  result.insert(check_for_temporal_macros_bmh(str, pos));
 
   return result;
 }
 #endif
 
-int
+HashSourceCodeResult
 do_hash_file(const Context& ctx,
              Digest& digest,
              const std::string& path,
@@ -191,9 +187,9 @@ do_hash_file(const Context& ctx,
     check_temporal_macros ? InodeCache::ContentType::checked_for_temporal_macros
                           : InodeCache::ContentType::raw;
   if (ctx.config.inode_cache()) {
-    int result;
-    if (ctx.inode_cache.get(path, content_type, digest, &result)) {
-      return result;
+    const auto result = ctx.inode_cache.get(path, content_type, digest);
+    if (result) {
+      return *result;
     }
   }
 #else
@@ -203,12 +199,12 @@ do_hash_file(const Context& ctx,
   const auto data = util::read_file<std::string>(path, size_hint);
   if (!data) {
     LOG("Failed to read {}: {}", path, data.error());
-    return HASH_SOURCE_CODE_ERROR;
+    return HashSourceCodeResult(HashSourceCode::error);
   }
 
-  int result = HASH_SOURCE_CODE_OK;
+  HashSourceCodeResult result;
   if (check_temporal_macros) {
-    result |= check_for_temporal_macros(*data);
+    result.insert(check_for_temporal_macros(*data));
   }
 
   Hash hash;
@@ -224,7 +220,7 @@ do_hash_file(const Context& ctx,
 
 } // namespace
 
-int
+HashSourceCodeResult
 check_for_temporal_macros(std::string_view str)
 {
 #ifdef HAVE_AVX2
@@ -235,23 +231,23 @@ check_for_temporal_macros(std::string_view str)
   return check_for_temporal_macros_bmh(str);
 }
 
-int
+HashSourceCodeResult
 hash_source_code_file(const Context& ctx,
                       Digest& digest,
                       const std::string& path,
                       size_t size_hint)
 {
   const bool check_temporal_macros =
-    !ctx.config.sloppiness().is_enabled(core::Sloppy::time_macros);
-  int result =
+    !ctx.config.sloppiness().contains(core::Sloppy::time_macros);
+  auto result =
     do_hash_file(ctx, digest, path, size_hint, check_temporal_macros);
 
-  if (!check_temporal_macros || result == HASH_SOURCE_CODE_OK
-      || (result & HASH_SOURCE_CODE_ERROR)) {
+  if (!check_temporal_macros || result.empty()
+      || result.contains(HashSourceCode::error)) {
     return result;
   }
 
-  if (result & HASH_SOURCE_CODE_FOUND_TIME) {
+  if (result.contains(HashSourceCode::found_time)) {
     // We don't know for sure that the program actually uses the __TIME__ macro,
     // but we have to assume it anyway and hash the time stamp. However, that's
     // not very useful since the chance that we get a cache hit later the same
@@ -269,13 +265,14 @@ hash_source_code_file(const Context& ctx,
   Hash hash;
   hash.hash(digest.to_string());
 
-  if (result & HASH_SOURCE_CODE_FOUND_DATE) {
+  if (result.contains(HashSourceCode::found_date)) {
     LOG("Found __DATE__ in {}", path);
 
     hash.hash_delimiter("date");
     auto now = Util::localtime();
     if (!now) {
-      return HASH_SOURCE_CODE_ERROR;
+      result.insert(HashSourceCode::error);
+      return result;
     }
     hash.hash(now->tm_year);
     hash.hash(now->tm_mon);
@@ -291,17 +288,19 @@ hash_source_code_file(const Context& ctx,
     }
   }
 
-  if (result & HASH_SOURCE_CODE_FOUND_TIMESTAMP) {
+  if (result.contains(HashSourceCode::found_timestamp)) {
     LOG("Found __TIMESTAMP__ in {}", path);
 
     const auto stat = Stat::stat(path);
     if (!stat) {
-      return HASH_SOURCE_CODE_ERROR;
+      result.insert(HashSourceCode::error);
+      return result;
     }
 
     auto modified_time = Util::localtime(stat.mtime());
     if (!modified_time) {
-      return HASH_SOURCE_CODE_ERROR;
+      result.insert(HashSourceCode::error);
+      return result;
     }
     hash.hash_delimiter("timestamp");
 #ifdef HAVE_ASCTIME_R
@@ -311,7 +310,8 @@ hash_source_code_file(const Context& ctx,
     auto timestamp = asctime(&*modified_time);
 #endif
     if (!timestamp) {
-      return HASH_SOURCE_CODE_ERROR;
+      result.insert(HashSourceCode::error);
+      return result;
     }
     hash.hash(timestamp);
   }
@@ -326,8 +326,7 @@ hash_binary_file(const Context& ctx,
                  const std::string& path,
                  size_t size_hint)
 {
-  return do_hash_file(ctx, digest, path, size_hint, false)
-         == HASH_SOURCE_CODE_OK;
+  return do_hash_file(ctx, digest, path, size_hint, false).empty();
 }
 
 bool
index d0fd142..c270b38 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include <util/BitSet.hpp>
+
 #include <cstddef>
 #include <string>
 #include <string_view>
@@ -27,26 +29,24 @@ class Context;
 class Digest;
 class Hash;
 
-const int HASH_SOURCE_CODE_OK = 0;
-const int HASH_SOURCE_CODE_ERROR = (1 << 0);
-const int HASH_SOURCE_CODE_FOUND_DATE = (1 << 1);
-const int HASH_SOURCE_CODE_FOUND_TIME = (1 << 2);
-const int HASH_SOURCE_CODE_FOUND_TIMESTAMP = (1 << 3);
+enum class HashSourceCode {
+  ok = 0,
+  error = 1U << 0,
+  found_date = 1U << 1,
+  found_time = 1U << 2,
+  found_timestamp = 1U << 3,
+};
 
-// Search for the strings "DATE", "TIME" and "TIMESTAMP" with two surrounding
-// underscores in `str`.
-//
-// Returns a bitmask with HASH_SOURCE_CODE_FOUND_DATE,
-// HASH_SOURCE_CODE_FOUND_TIME and HASH_SOURCE_CODE_FOUND_TIMESTAMP set
-// appropriately.
-int check_for_temporal_macros(std::string_view str);
+using HashSourceCodeResult = util::BitSet<HashSourceCode>;
+
+// Search for tokens (described in HashSourceCode) in `str`.
+HashSourceCodeResult check_for_temporal_macros(std::string_view str);
 
-// Hash a source code file using the inode cache if enabled. Returns a bitmask
-// of HASH_SOURCE_CODE_* results.
-int hash_source_code_file(const Context& ctx,
-                          Digest& digest,
-                          const std::string& path,
-                          size_t size_hint = 0);
+// Hash a source code file using the inode cache if enabled.
+HashSourceCodeResult hash_source_code_file(const Context& ctx,
+                                           Digest& digest,
+                                           const std::string& path,
+                                           size_t size_hint = 0);
 
 // Hash a binary file (using the inode cache if enabled) and put its digest in
 // `digest`
index 5c843db..986db6d 100644 (file)
@@ -114,6 +114,20 @@ to_string(const RemoteStorageConfig& entry)
   return result;
 }
 
+static Url
+url_from_string(const std::string& url_string)
+{
+  // The Url class is parsing the URL object lazily. Check if the URL is valid
+  // now to avoid exceptions later.
+  try {
+    Url url(url_string);
+    std::ignore = url.str();
+    return url;
+  } catch (const std::exception& e) {
+    throw core::Error(FMT("Cannot parse URL {}: {}", url_string, e.what()));
+  }
+}
+
 static RemoteStorageConfig
 parse_storage_config(const std::string_view entry)
 {
@@ -127,15 +141,7 @@ parse_storage_config(const std::string_view entry)
 
   RemoteStorageConfig result;
   const auto url_str = std::string(parts[0]);
-  result.params.url = url_str;
-
-  // The Url class is parsing the URL object lazily. Check if the URL is valid
-  // now to avoid exceptions later.
-  try {
-    std::ignore = result.params.url.str();
-  } catch (const std::exception& e) {
-    throw core::Error(FMT("Cannot parse URL {}: {}", url_str, e.what()));
-  }
+  result.params.url = url_from_string(url_str);
 
   if (result.params.url.scheme().empty()) {
     throw core::Error(FMT("URL scheme must not be empty: {}", entry));
@@ -369,7 +375,7 @@ get_shard_url(const Digest& key,
     }
   }
 
-  return util::replace_first(url, "*", best_shard);
+  return url_from_string(util::replace_first(url, "*", best_shard));
 }
 
 RemoteStorageBackendEntry*
@@ -461,9 +467,6 @@ Storage::get_from_remote_storage(const Digest& key,
           backend->url_for_logging,
           ms);
       local.increment_statistic(core::Statistic::remote_storage_read_miss);
-      if (type == core::CacheEntryType::result) {
-        local.increment_statistic(core::Statistic::remote_storage_miss);
-      }
     }
   }
 }
index 7b0d4f0..01e0086 100644 (file)
@@ -1,10 +1,6 @@
 set(
   sources
-  CacheFile.cpp
   LocalStorage.cpp
-  LocalStorage_cleanup.cpp
-  LocalStorage_compress.cpp
-  LocalStorage_statistics.cpp
   StatsFile.cpp
   util.cpp
 )
index 001e2a9..020d096 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #include <AtomicFile.hpp>
 #include <Config.hpp>
+#include <Context.hpp>
+#include <File.hpp>
 #include <Logging.hpp>
 #include <MiniTrace.hpp>
+#include <TemporaryFile.hpp>
+#include <ThreadPool.hpp>
 #include <Util.hpp>
 #include <assertions.hpp>
+#include <core/CacheEntry.hpp>
+#include <core/FileRecompressor.hpp>
+#include <core/Manifest.hpp>
+#include <core/Result.hpp>
+#include <core/Statistics.hpp>
 #include <core/exceptions.hpp>
 #include <core/wincompat.hpp>
 #include <fmtmacros.hpp>
 #include <storage/local/StatsFile.hpp>
+#include <storage/local/util.hpp>
 #include <util/Duration.hpp>
+#include <util/TextTable.hpp>
+#include <util/expected.hpp>
 #include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <third_party/fmt/core.h>
+
+#ifdef INODE_CACHE_SUPPORTED
+#  include <InodeCache.hpp>
+#endif
+
+#include <algorithm>
+#include <atomic>
+#include <cstdlib>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <utility>
 
 #ifdef HAVE_UNISTD_H
 #  include <unistd.h>
 #endif
 
 using core::Statistic;
+using core::StatisticsCounters;
 
 namespace storage::local {
 
@@ -64,6 +92,67 @@ const uint8_t k_min_cache_levels = 2;
 // k_max_cache_files_per_directory.
 const uint8_t k_max_cache_levels = 4;
 
+namespace {
+
+struct Level2Counters
+{
+  uint64_t files = 0;
+  uint64_t size = 0;
+
+  Level2Counters&
+  operator+=(const Level2Counters& other)
+  {
+    files += other.files;
+    size += other.size;
+    return *this;
+  }
+};
+
+struct Level1Counters
+{
+  Level2Counters level_2_counters[16] = {};
+  uint64_t cleanups = 0;
+
+  uint64_t
+  files() const
+  {
+    uint64_t sum = 0;
+    for (const auto& cs : level_2_counters) {
+      sum += cs.files;
+    }
+    return sum;
+  }
+
+  uint64_t
+  size() const
+  {
+    uint64_t sum = 0;
+    for (const auto& cs : level_2_counters) {
+      sum += cs.size;
+    }
+    return sum;
+  }
+};
+
+} // namespace
+
+static void
+set_counters(const StatsFile& stats_file, const Level1Counters& level_1_cs)
+{
+  stats_file.update([&](auto& cs) {
+    cs.set(Statistic::files_in_cache, level_1_cs.files());
+    cs.set(Statistic::cache_size_kibibyte, level_1_cs.size() / 1024);
+    for_each_cache_subdir([&](uint8_t i) {
+      cs.set_offsetted(
+        Statistic::subdir_files_base, i, level_1_cs.level_2_counters[i].files);
+      cs.set_offsetted(Statistic::subdir_size_kibibyte_base,
+                       i,
+                       level_1_cs.level_2_counters[i].size / 1024);
+    });
+    cs.increment(Statistic::cleanups_performed, level_1_cs.cleanups);
+  });
+}
+
 static std::string
 suffix_from_type(const core::CacheEntryType type)
 {
@@ -91,89 +180,205 @@ calculate_wanted_cache_level(const uint64_t files_in_level_1)
   return k_max_cache_levels;
 }
 
-LocalStorage::LocalStorage(const Config& config) : m_config(config)
+static void
+delete_file(const std::string& path,
+            const uint64_t size,
+            uint64_t& cache_size,
+            uint64_t& files_in_cache)
 {
+  const bool deleted = Util::unlink_safe(path, Util::UnlinkLog::ignore_failure);
+  if (!deleted && errno != ENOENT && errno != ESTALE) {
+    LOG("Failed to unlink {} ({})", path, strerror(errno));
+  } else {
+    // The counters are intentionally subtracted even if there was no file to
+    // delete since the final cache size calculation will be incorrect if they
+    // aren't. (This can happen when there are several parallel ongoing
+    // cleanups of the same directory.)
+    cache_size -= size;
+    --files_in_cache;
+  }
 }
 
-void
-LocalStorage::finalize()
+struct CleanDirResult
 {
-  if (m_config.temporary_dir() == m_config.default_temporary_dir()) {
-    clean_internal_tempdir();
-  }
+  Level2Counters before;
+  Level2Counters after;
+};
+
+static CleanDirResult
+clean_dir(
+  const std::string& l2_dir,
+  const uint64_t max_size,
+  const uint64_t max_files,
+  const std::optional<uint64_t> max_age = std::nullopt,
+  const std::optional<std::string> namespace_ = std::nullopt,
+  const ProgressReceiver& progress_receiver = [](double /*progress*/) {})
+{
+  LOG("Cleaning up cache directory {}", l2_dir);
 
-  if (!m_config.stats()) {
-    return;
-  }
+  auto files = get_cache_dir_files(l2_dir);
+  progress_receiver(1.0 / 3);
+
+  uint64_t cache_size = 0;
+  uint64_t files_in_cache = 0;
+  auto current_time = util::TimePoint::now();
+  std::unordered_map<std::string /*result_file*/,
+                     std::vector<std::string> /*associated_raw_files*/>
+    raw_files_map;
+
+  for (size_t i = 0; i < files.size();
+       ++i, progress_receiver(1.0 / 3 + 1.0 * i / files.size() / 3)) {
+    const auto& file = files[i];
+
+    if (!file.is_regular()) {
+      // Not a file or missing file.
+      continue;
+    }
+
+    // Delete any tmp files older than 1 hour right away.
+    if (file.mtime() + util::Duration(3600) < current_time
+        && TemporaryFile::is_tmp_file(file.path())) {
+      Util::unlink_tmp(file.path());
+      continue;
+    }
+
+    if (namespace_ && file_type_from_path(file.path()) == FileType::raw) {
+      const auto result_filename =
+        FMT("{}R", file.path().substr(0, file.path().length() - 2));
+      raw_files_map[result_filename].push_back(file.path());
+    }
 
-  if (m_manifest_key) {
-    // A manifest entry was written.
-    ASSERT(!m_manifest_path.empty());
-    update_stats_and_maybe_move_cache_file(*m_manifest_key,
-                                           m_manifest_path,
-                                           m_manifest_counter_updates,
-                                           core::CacheEntryType::manifest);
+    cache_size += file.size_on_disk();
+    files_in_cache += 1;
   }
 
-  if (!m_result_key) {
-    // No result entry was written, so we just choose one of the stats files in
-    // the 256 level 2 directories.
+  // Sort according to modification time, oldest first.
+  std::sort(files.begin(), files.end(), [](const auto& f1, const auto& f2) {
+    return f1.mtime() < f2.mtime();
+  });
 
-    ASSERT(m_result_counter_updates.get(Statistic::cache_size_kibibyte) == 0);
-    ASSERT(m_result_counter_updates.get(Statistic::files_in_cache) == 0);
+  LOG("Before cleanup: {:.0f} KiB, {:.0f} files",
+      static_cast<double>(cache_size) / 1024,
+      static_cast<double>(files_in_cache));
+  Level2Counters counters_before{files_in_cache, cache_size};
 
-    const auto bucket = getpid() % 256;
-    const auto stats_file =
-      FMT("{}/{:x}/{:x}/stats", m_config.cache_dir(), bucket / 16, bucket % 16);
-    StatsFile(stats_file).update([&](auto& cs) {
-      cs.increment(m_result_counter_updates);
-    });
-    return;
+  bool cleaned = false;
+  for (size_t i = 0; i < files.size();
+       ++i, progress_receiver(2.0 / 3 + 1.0 * i / files.size() / 3)) {
+    const auto& file = files[i];
+
+    if (!file || file.is_directory()) {
+      continue;
+    }
+
+    if ((max_size == 0 || cache_size <= max_size)
+        && (max_files == 0 || files_in_cache <= max_files)
+        && (!max_age
+            || file.mtime() > (current_time - util::Duration(*max_age)))
+        && (!namespace_ || max_age)) {
+      break;
+    }
+
+    if (namespace_) {
+      try {
+        core::CacheEntry::Header header(file.path());
+        if (header.namespace_ != *namespace_) {
+          continue;
+        }
+      } catch (core::Error&) {
+        // Failed to read header: ignore.
+        continue;
+      }
+
+      // For namespace eviction we need to remove raw files based on result
+      // filename since they don't have a header.
+      if (file_type_from_path(file.path()) == FileType::result) {
+        const auto entry = raw_files_map.find(file.path());
+        if (entry != raw_files_map.end()) {
+          for (const auto& raw_file : entry->second) {
+            delete_file(raw_file,
+                        Stat::lstat(raw_file).size_on_disk(),
+                        cache_size,
+                        files_in_cache);
+          }
+        }
+      }
+    }
+
+    delete_file(file.path(), file.size_on_disk(), cache_size, files_in_cache);
+    cleaned = true;
   }
 
-  ASSERT(!m_result_path.empty());
+  LOG("After cleanup: {:.0f} KiB, {:.0f} files",
+      static_cast<double>(cache_size) / 1024,
+      static_cast<double>(files_in_cache));
+  Level2Counters counters_after{files_in_cache, cache_size};
 
-  const auto counters =
-    update_stats_and_maybe_move_cache_file(*m_result_key,
-                                           m_result_path,
-                                           m_result_counter_updates,
-                                           core::CacheEntryType::result);
-  if (!counters) {
-    return;
+  if (cleaned) {
+    LOG("Cleaned up cache directory {}", l2_dir);
   }
 
-  const auto subdir =
-    FMT("{}/{:x}", m_config.cache_dir(), m_result_key->bytes()[0] >> 4);
-  bool need_cleanup = false;
+  return {counters_before, counters_after};
+}
 
-  if (m_config.max_files() != 0
-      && counters->get(Statistic::files_in_cache) > m_config.max_files() / 16) {
-    LOG("Need to clean up {} since it holds {} files (limit: {} files)",
-        subdir,
-        counters->get(Statistic::files_in_cache),
-        m_config.max_files() / 16);
-    need_cleanup = true;
+FileType
+file_type_from_path(std::string_view path)
+{
+  if (util::ends_with(path, "M")) {
+    return FileType::manifest;
+  } else if (util::ends_with(path, "R")) {
+    return FileType::result;
+  } else if (util::ends_with(path, "W")) {
+    return FileType::raw;
+  } else {
+    return FileType::unknown;
   }
-  if (m_config.max_size() != 0
-      && counters->get(Statistic::cache_size_kibibyte)
-           > m_config.max_size() / 1024 / 16) {
-    LOG("Need to clean up {} since it holds {} KiB (limit: {} KiB)",
-        subdir,
-        counters->get(Statistic::cache_size_kibibyte),
-        m_config.max_size() / 1024 / 16);
-    need_cleanup = true;
+}
+
+LocalStorage::LocalStorage(const Config& config) : m_config(config)
+{
+}
+
+void
+LocalStorage::finalize()
+{
+  if (m_config.stats() && !m_counter_updates.all_zero()) {
+    // Pseudo-randomly choose one of the stats files in the 256 level 2
+    // directories.
+    const auto bucket = getpid() % 256;
+    const uint8_t l1_index = bucket / 16;
+    const uint8_t l2_index = bucket % 16;
+    const auto l2_stats_file = get_stats_file(l1_index, l2_index);
+
+    uint64_t l2_files_in_cache = 0;
+    uint64_t l2_cache_size_kibibyte = 0;
+
+    l2_stats_file.update([&](auto& cs) {
+      cs.increment(m_counter_updates);
+
+      if (m_stored_data) {
+        // Ccache 4.8-4.8.2 erroneously stored files/size counters for raw files
+        // in L2, so move them to L1 to make the cleanup algorithm aware.
+        l2_files_in_cache = cs.get(Statistic::files_in_cache);
+        l2_cache_size_kibibyte = cs.get(Statistic::cache_size_kibibyte);
+        cs.set(Statistic::files_in_cache, 0);
+        cs.set(Statistic::cache_size_kibibyte, 0);
+      }
+    });
+
+    if (m_stored_data) {
+      // See comment about ccache 4.8-4.8.2 above.
+      if (l2_files_in_cache > 0 || l2_cache_size_kibibyte > 0) {
+        increment_files_and_size_counters(
+          l1_index, l2_index, l2_files_in_cache, l2_cache_size_kibibyte);
+      }
+
+      perform_automatic_cleanup();
+    }
   }
 
-  if (need_cleanup) {
-    const double factor = m_config.limit_multiple() / 16;
-    const uint64_t max_size = round(m_config.max_size() * factor);
-    const uint32_t max_files = round(m_config.max_files() * factor);
-    clean_dir(subdir,
-              max_size,
-              max_files,
-              std::nullopt,
-              std::nullopt,
-              [](double /*progress*/) {});
+  if (m_config.temporary_dir() == m_config.default_temporary_dir()) {
+    clean_internal_tempdir();
   }
 }
 
@@ -203,11 +408,10 @@ LocalStorage::get(const Digest& key, const core::CacheEntryType type)
     LOG("No {} in local storage", key.to_string());
   }
 
-  increment_statistic(return_value ? core::Statistic::local_storage_read_hit
-                                   : core::Statistic::local_storage_read_miss);
-  if (type == core::CacheEntryType::result) {
-    increment_statistic(return_value ? core::Statistic::local_storage_hit
-                                     : core::Statistic::local_storage_miss);
+  increment_statistic(return_value ? Statistic::local_storage_read_hit
+                                   : Statistic::local_storage_read_miss);
+  if (return_value && type == core::CacheEntryType::result) {
+    increment_statistic(Statistic::local_storage_hit);
   }
 
   return return_value;
@@ -228,43 +432,49 @@ LocalStorage::put(const Digest& key,
     return;
   }
 
-  switch (type) {
-  case core::CacheEntryType::manifest:
-    m_manifest_key = key;
-    m_manifest_path = cache_file.path;
-    break;
-
-  case core::CacheEntryType::result:
-    m_result_key = key;
-    m_result_path = cache_file.path;
-    break;
-  }
+  auto l2_content_lock = get_level_2_content_lock(key);
 
   try {
-    increment_statistic(core::Statistic::local_storage_write);
     AtomicFile result_file(cache_file.path, AtomicFile::Mode::binary);
     result_file.write(value);
+    result_file.flush();
+    if (!l2_content_lock.acquire()) {
+      LOG("Not storing {} due to lock failure", cache_file.path);
+      return;
+    }
     result_file.commit();
   } catch (core::Error& e) {
     LOG("Failed to write to {}: {}", cache_file.path, e.what());
     return;
   }
 
+  LOG("Stored {} in local storage ({})", key.to_string(), cache_file.path);
+  m_stored_data = true;
+
+  if (!m_config.stats()) {
+    return;
+  }
+
+  increment_statistic(Statistic::local_storage_write);
+
   const auto new_stat = Stat::stat(cache_file.path, Stat::OnError::log);
   if (!new_stat) {
-    LOG("Failed to stat {}: {}", cache_file.path, strerror(errno));
     return;
   }
 
-  LOG("Stored {} in local storage ({})", key.to_string(), cache_file.path);
+  int64_t files_change = cache_file.stat ? 0 : 1;
+  int64_t size_change_kibibyte =
+    Util::size_change_kibibyte(cache_file.stat, new_stat);
+  auto counters =
+    increment_files_and_size_counters(key, files_change, size_change_kibibyte);
 
-  auto& counter_updates = (type == core::CacheEntryType::manifest)
-                            ? m_manifest_counter_updates
-                            : m_result_counter_updates;
-  counter_updates.increment(
-    Statistic::cache_size_kibibyte,
-    Util::size_change_kibibyte(cache_file.stat, new_stat));
-  counter_updates.increment(Statistic::files_in_cache, cache_file.stat ? 0 : 1);
+  l2_content_lock.release();
+
+  if (!counters) {
+    return;
+  }
+
+  move_to_wanted_cache_level(*counters, key, type, cache_file.path);
 
   // Make sure we have a CACHEDIR.TAG in the cache part of cache_dir. This can
   // be done almost anywhere, but we might as well do it near the end as we save
@@ -279,13 +489,24 @@ LocalStorage::remove(const Digest& key, const core::CacheEntryType type)
   MTR_SCOPE("local_storage", "remove");
 
   const auto cache_file = look_up_cache_file(key, type);
-  if (cache_file.stat) {
-    increment_statistic(core::Statistic::local_storage_write);
-    Util::unlink_safe(cache_file.path);
-    LOG("Removed {} from local storage ({})", key.to_string(), cache_file.path);
-  } else {
+  if (!cache_file.stat) {
     LOG("No {} to remove from local storage", key.to_string());
+    return;
   }
+
+  increment_statistic(Statistic::local_storage_write);
+
+  {
+    auto l2_content_lock = get_level_2_content_lock(key);
+    if (!l2_content_lock.acquire()) {
+      LOG("Not removing {} due to lock failure", cache_file.path);
+    }
+    Util::unlink_safe(cache_file.path);
+  }
+
+  LOG("Removed {} from local storage ({})", key.to_string(), cache_file.path);
+  increment_files_and_size_counters(
+    key, -1, -static_cast<int64_t>(cache_file.stat.size_on_disk() / 1024));
 }
 
 std::string
@@ -320,6 +541,9 @@ LocalStorage::put_raw_files(
   const auto cache_file = look_up_cache_file(key, core::CacheEntryType::result);
   Util::ensure_dir_exists(Util::dir_name(cache_file.path));
 
+  int64_t files_change = 0;
+  int64_t size_kibibyte_change = 0;
+
   for (auto [file_number, source_path] : raw_files) {
     const auto dest_path = get_raw_file_path(cache_file.path, file_number);
     const auto old_stat = Stat::stat(dest_path);
@@ -335,28 +559,327 @@ LocalStorage::put_raw_files(
       throw;
     }
     const auto new_stat = Stat::stat(dest_path);
-    increment_statistic(Statistic::cache_size_kibibyte,
-                        Util::size_change_kibibyte(old_stat, new_stat));
-    increment_statistic(Statistic::files_in_cache,
-                        (new_stat ? 1 : 0) - (old_stat ? 1 : 0));
+    files_change += (new_stat ? 1 : 0) - (old_stat ? 1 : 0);
+    size_kibibyte_change += Util::size_change_kibibyte(old_stat, new_stat);
   }
+
+  increment_files_and_size_counters(key, files_change, size_kibibyte_change);
 }
 
 void
 LocalStorage::increment_statistic(const Statistic statistic,
                                   const int64_t value)
 {
-  m_result_counter_updates.increment(statistic, value);
+  if (m_config.stats()) {
+    m_counter_updates.increment(statistic, value);
+  }
+}
+
+void
+LocalStorage::increment_statistics(const StatisticsCounters& statistics)
+{
+  if (m_config.stats()) {
+    m_counter_updates.increment(statistics);
+  }
+}
+
+// Zero all statistics counters except those tracking cache size and number of
+// files in the cache.
+void
+LocalStorage::zero_all_statistics()
+{
+  const auto now = util::TimePoint::now();
+  const auto zeroable_fields = core::Statistics::get_zeroable_fields();
+
+  for_each_level_1_and_2_stats_file(
+    m_config.cache_dir(), [=](const std::string& path) {
+      StatsFile(path).update([=](auto& cs) {
+        for (const auto statistic : zeroable_fields) {
+          cs.set(statistic, 0);
+        }
+        cs.set(Statistic::stats_zeroed_timestamp, now.sec());
+      });
+    });
+}
+
+// Get statistics and last time of update for the whole local storage cache.
+std::pair<StatisticsCounters, util::TimePoint>
+LocalStorage::get_all_statistics() const
+{
+  StatisticsCounters counters;
+  uint64_t zero_timestamp = 0;
+  util::TimePoint last_updated;
+
+  // Add up the stats in each directory.
+  for_each_level_1_and_2_stats_file(
+    m_config.cache_dir(), [&](const auto& path) {
+      counters.set(Statistic::stats_zeroed_timestamp, 0); // Don't add
+      counters.increment(StatsFile(path).read());
+      zero_timestamp = std::max(counters.get(Statistic::stats_zeroed_timestamp),
+                                zero_timestamp);
+      last_updated = std::max(last_updated, Stat::stat(path).mtime());
+    });
+
+  counters.set(Statistic::stats_zeroed_timestamp, zero_timestamp);
+  return {counters, last_updated};
+}
+
+void
+LocalStorage::evict(const ProgressReceiver& progress_receiver,
+                    std::optional<uint64_t> max_age,
+                    std::optional<std::string> namespace_)
+{
+  return do_clean_all(progress_receiver, 0, 0, max_age, namespace_);
+}
+
+void
+LocalStorage::clean_all(const ProgressReceiver& progress_receiver)
+{
+  return do_clean_all(progress_receiver,
+                      m_config.max_size(),
+                      m_config.max_files(),
+                      std::nullopt,
+                      std::nullopt);
 }
 
+// Wipe all cached files in all subdirectories.
 void
-LocalStorage::increment_statistics(const core::StatisticsCounters& statistics)
+LocalStorage::wipe_all(const ProgressReceiver& progress_receiver)
+{
+  util::LongLivedLockFileManager lock_manager;
+
+  for_each_cache_subdir(
+    progress_receiver, [&](uint8_t l1_index, const auto& l1_progress_receiver) {
+      auto acquired_locks =
+        acquire_all_level_2_content_locks(lock_manager, l1_index);
+      Level1Counters level_1_counters;
+
+      for_each_cache_subdir(
+        l1_progress_receiver,
+        [&](uint8_t l2_index, const ProgressReceiver& l2_progress_receiver) {
+          auto l2_dir = get_subdir(l1_index, l2_index);
+          const auto files = get_cache_dir_files(l2_dir);
+          l2_progress_receiver(0.5);
+
+          for (size_t i = 0; i < files.size(); ++i) {
+            Util::unlink_safe(files[i].path());
+            l2_progress_receiver(0.5 + 0.5 * i / files.size());
+          }
+
+          if (!files.empty()) {
+            ++level_1_counters.cleanups;
+          }
+        });
+
+      set_counters(get_stats_file(l1_index), level_1_counters);
+    });
+}
+
+CompressionStatistics
+LocalStorage::get_compression_statistics(
+  const ProgressReceiver& progress_receiver) const
 {
-  m_result_counter_updates.increment(statistics);
+  CompressionStatistics cs{};
+
+  for_each_cache_subdir(
+    progress_receiver,
+    [&](const auto& l1_index, const auto& l1_progress_receiver) {
+      for_each_cache_subdir(
+        l1_progress_receiver,
+        [&](const auto& l2_index, const auto& l2_progress_receiver) {
+          auto l2_dir = get_subdir(l1_index, l2_index);
+          const auto files = get_cache_dir_files(l2_dir);
+          l2_progress_receiver(0.2);
+
+          for (size_t i = 0; i < files.size(); ++i) {
+            const auto& cache_file = files[i];
+            try {
+              core::CacheEntry::Header header(cache_file.path());
+              cs.actual_size += cache_file.size_on_disk();
+              cs.content_size += util::likely_size_on_disk(header.entry_size);
+            } catch (core::Error&) {
+              cs.incompressible_size += cache_file.size_on_disk();
+            }
+            l2_progress_receiver(0.2 + 0.8 * i / files.size());
+          }
+        });
+    });
+
+  return cs;
+}
+
+void
+LocalStorage::recompress(const std::optional<int8_t> level,
+                         const uint32_t threads,
+                         const ProgressReceiver& progress_receiver)
+{
+  const size_t read_ahead =
+    std::max(static_cast<size_t>(10), 2 * static_cast<size_t>(threads));
+  ThreadPool thread_pool(threads, read_ahead);
+  core::FileRecompressor recompressor;
+
+  std::atomic<uint64_t> incompressible_size = 0;
+  util::LongLivedLockFileManager lock_manager;
+
+  for_each_cache_subdir(
+    progress_receiver,
+    [&](const auto& l1_index, const auto& l1_progress_receiver) {
+      for_each_cache_subdir(
+        l1_progress_receiver,
+        [&](const auto& l2_index, const auto& l2_progress_receiver) {
+          auto l2_content_lock = get_level_2_content_lock(l1_index, l2_index);
+          l2_content_lock.make_long_lived(lock_manager);
+          if (!l2_content_lock.acquire()) {
+            // LOG_RAW+fmt::format instead of LOG due to GCC 12.3 bug #109241
+            LOG_RAW(fmt::format(
+              "Failed to acquire content lock for {}/{}", l1_index, l2_index));
+            return;
+          }
+
+          auto l2_dir = get_subdir(l1_index, l2_index);
+          auto files = get_cache_dir_files(l2_dir);
+          l2_progress_receiver(0.1);
+
+          auto stats_file = get_stats_file(l1_index);
+
+          for (size_t i = 0; i < files.size(); ++i) {
+            const auto& file = files[i];
+
+            if (file_type_from_path(file.path()) != FileType::unknown) {
+              thread_pool.enqueue([=, &recompressor, &incompressible_size] {
+                try {
+                  Stat new_stat = recompressor.recompress(
+                    file, level, core::FileRecompressor::KeepAtime::no);
+                  auto size_change_kibibyte =
+                    Util::size_change_kibibyte(file, new_stat);
+                  if (size_change_kibibyte != 0) {
+                    StatsFile(stats_file).update([=](auto& cs) {
+                      cs.increment(Statistic::cache_size_kibibyte,
+                                   size_change_kibibyte);
+                      cs.increment_offsetted(
+                        Statistic::subdir_size_kibibyte_base,
+                        l2_index,
+                        size_change_kibibyte);
+                    });
+                  }
+                } catch (core::Error&) {
+                  // Ignore for now.
+                  incompressible_size += file.size_on_disk();
+                }
+              });
+            } else if (!TemporaryFile::is_tmp_file(file.path())) {
+              incompressible_size += file.size_on_disk();
+            }
+
+            l2_progress_receiver(0.1 + 0.9 * i / files.size());
+          }
+
+          if (util::ends_with(l2_dir, "f/f")) {
+            // Wait here instead of after for_each_cache_subdir to avoid
+            // updating the progress bar to 100% before all work is done.
+            thread_pool.shut_down();
+          }
+        });
+    });
+
+  // In case there was no f/f subdir, shut down the thread pool now.
+  thread_pool.shut_down();
+
+  if (isatty(STDOUT_FILENO)) {
+    PRINT_RAW(stdout, "\n\n");
+  }
+
+  const double old_ratio = recompressor.old_size() > 0
+                             ? static_cast<double>(recompressor.content_size())
+                                 / recompressor.old_size()
+                             : 0.0;
+  const double old_savings =
+    old_ratio > 0.0 ? 100.0 - (100.0 / old_ratio) : 0.0;
+  const double new_ratio = recompressor.new_size() > 0
+                             ? static_cast<double>(recompressor.content_size())
+                                 / recompressor.new_size()
+                             : 0.0;
+  const double new_savings =
+    new_ratio > 0.0 ? 100.0 - (100.0 / new_ratio) : 0.0;
+  const int64_t size_diff = static_cast<int64_t>(recompressor.new_size())
+                            - static_cast<int64_t>(recompressor.old_size());
+
+  auto human_readable = [&](uint64_t size) {
+    return util::format_human_readable_size(size,
+                                            m_config.size_unit_prefix_type());
+  };
+
+  const auto [old_compr_size_quantity, old_compr_size_unit] =
+    util::split_once(human_readable(recompressor.old_size()), ' ');
+  ASSERT(old_compr_size_unit);
+  const auto [new_compr_size_quantity, new_compr_size_unit] =
+    util::split_once(human_readable(recompressor.new_size()), ' ');
+  ASSERT(new_compr_size_unit);
+  const auto [content_size_quantity, content_size_unit] =
+    util::split_once(human_readable(recompressor.content_size()), ' ');
+  ASSERT(content_size_unit);
+  const auto [incompr_size_quantity, incompr_size_unit] =
+    util::split_once(human_readable(incompressible_size), ' ');
+  ASSERT(incompr_size_unit);
+  const auto [size_diff_quantity, size_diff_unit] =
+    util::split_once(human_readable(std::abs(size_diff)), ' ');
+  ASSERT(size_diff_unit);
+
+  using C = util::TextTable::Cell;
+  util::TextTable table;
+
+  table.add_row({
+    "Original data:",
+    C(content_size_quantity).right_align(),
+    *content_size_unit,
+  });
+  table.add_row({
+    "Old compressed data:",
+    C(old_compr_size_quantity).right_align(),
+    *old_compr_size_unit,
+    FMT("({:.1f}% of original size)", 100.0 - old_savings),
+  });
+  table.add_row({
+    "  Compression ratio:",
+    C(FMT("{:5.3f}", old_ratio)).right_align(),
+    "x",
+    FMT("({:.1f}% space savings)", old_savings),
+  });
+  table.add_row({
+    "New compressed data:",
+    C(new_compr_size_quantity).right_align(),
+    *new_compr_size_unit,
+    FMT("({:.1f}% of original size)", 100.0 - new_savings),
+  });
+  table.add_row({
+    "  Compression ratio:",
+    C(FMT("{:5.3f}", new_ratio)).right_align(),
+    "x",
+    FMT("({:.1f}% space savings)", new_savings),
+  });
+  table.add_row({
+    "Size change:",
+    C(FMT("{}{}", size_diff < 0 ? "-" : "", size_diff_quantity)).right_align(),
+    *size_diff_unit,
+  });
+
+  PRINT_RAW(stdout, table.render());
 }
 
 // Private methods
 
+std::string
+LocalStorage::get_subdir(uint8_t l1_index) const
+{
+  return FMT("{}/{:x}", m_config.cache_dir(), l1_index);
+}
+
+std::string
+LocalStorage::get_subdir(uint8_t l1_index, uint8_t l2_index) const
+{
+  return FMT("{}/{:x}/{:x}", m_config.cache_dir(), l1_index, l2_index);
+}
+
 LocalStorage::LookUpCacheFileResult
 LocalStorage::look_up_cache_file(const Digest& key,
                                  const core::CacheEntryType type) const
@@ -377,6 +900,408 @@ LocalStorage::look_up_cache_file(const Digest& key,
   return {shallowest_path, Stat(), k_min_cache_levels};
 }
 
+StatsFile
+LocalStorage::get_stats_file(uint8_t l1_index) const
+{
+  return StatsFile(FMT("{}/{:x}/stats", m_config.cache_dir(), l1_index));
+}
+
+StatsFile
+LocalStorage::get_stats_file(uint8_t l1_index, uint8_t l2_index) const
+{
+  return StatsFile(
+    FMT("{}/{:x}/{:x}/stats", m_config.cache_dir(), l1_index, l2_index));
+}
+
+void
+LocalStorage::move_to_wanted_cache_level(const StatisticsCounters& counters,
+                                         const Digest& key,
+                                         core::CacheEntryType type,
+                                         const std::string& cache_file_path)
+{
+  const auto wanted_level =
+    calculate_wanted_cache_level(counters.get(Statistic::files_in_cache));
+  const auto wanted_path =
+    get_path_in_cache(wanted_level, key.to_string() + suffix_from_type(type));
+  if (cache_file_path != wanted_path) {
+    Util::ensure_dir_exists(Util::dir_name(wanted_path));
+    LOG("Moving {} to {}", cache_file_path, wanted_path);
+    try {
+      Util::rename(cache_file_path, wanted_path);
+    } catch (const core::Error&) {
+      // Two ccache processes may move the file at the same time, so failure
+      // to rename is OK.
+    }
+    for (const auto& raw_file : m_added_raw_files) {
+      try {
+        Util::rename(
+          raw_file,
+          FMT("{}/{}", Util::dir_name(wanted_path), Util::base_name(raw_file)));
+      } catch (const core::Error&) {
+        // Two ccache processes may move the file at the same time, so failure
+        // to rename is OK.
+      }
+    }
+  }
+}
+
+void
+LocalStorage::recount_level_1_dir(util::LongLivedLockFileManager& lock_manager,
+                                  uint8_t l1_index)
+{
+  auto acquired_locks =
+    acquire_all_level_2_content_locks(lock_manager, l1_index);
+  Level1Counters level_1_counters;
+
+  for_each_cache_subdir([&](uint8_t l2_index) {
+    auto files = get_cache_dir_files(get_subdir(l1_index, l2_index));
+    auto& level_2_counters = level_1_counters.level_2_counters[l2_index];
+    level_2_counters.files = files.size();
+    for (const auto& file : files) {
+      level_2_counters.size += file.size_on_disk();
+    }
+  });
+
+  set_counters(get_stats_file(l1_index), level_1_counters);
+}
+
+std::optional<core::StatisticsCounters>
+LocalStorage::increment_files_and_size_counters(uint8_t l1_index,
+                                                uint8_t l2_index,
+                                                int64_t files,
+                                                int64_t size_kibibyte)
+{
+  const auto level_1_stats_file = get_stats_file(l1_index);
+  return level_1_stats_file.update([&](auto& cs) {
+    // Level 1 counters:
+    cs.increment(Statistic::files_in_cache, files);
+    cs.increment(Statistic::cache_size_kibibyte, size_kibibyte);
+
+    // Level 2 counters:
+    cs.increment_offsetted(Statistic::subdir_files_base, l2_index, files);
+    cs.increment_offsetted(
+      Statistic::subdir_size_kibibyte_base, l2_index, size_kibibyte);
+  });
+}
+
+std::optional<core::StatisticsCounters>
+LocalStorage::increment_files_and_size_counters(const Digest& key,
+                                                int64_t files,
+                                                int64_t size_kibibyte)
+{
+  return increment_files_and_size_counters(
+    key.bytes()[0] >> 4, key.bytes()[0] & 0xF, files, size_kibibyte);
+}
+
+static uint8_t
+get_largest_level_2_index(const StatisticsCounters& counters)
+{
+  uint64_t largest_level_2_files = 0;
+  uint8_t largest_level_2_index = 0;
+  for_each_cache_subdir([&](uint8_t i) {
+    uint64_t l2_files =
+      1024 * counters.get_offsetted(Statistic::subdir_files_base, i);
+    if (l2_files > largest_level_2_files) {
+      largest_level_2_files = l2_files;
+      largest_level_2_index = i;
+    }
+  });
+  return largest_level_2_index;
+}
+
+static bool
+has_consistent_counters(const StatisticsCounters& counters)
+{
+  uint64_t level_2_files = 0;
+  uint64_t level_2_size_kibibyte = 0;
+  for_each_cache_subdir([&](uint8_t i) {
+    level_2_files += counters.get_offsetted(Statistic::subdir_files_base, i);
+    level_2_size_kibibyte +=
+      counters.get_offsetted(Statistic::subdir_size_kibibyte_base, i);
+  });
+  return level_2_files == counters.get(Statistic::files_in_cache)
+         && level_2_size_kibibyte
+              == counters.get(Statistic::cache_size_kibibyte);
+}
+
+void
+LocalStorage::perform_automatic_cleanup()
+{
+  util::LongLivedLockFileManager lock_manager;
+  auto auto_cleanup_lock = get_auto_cleanup_lock();
+  if (!auto_cleanup_lock.try_acquire()) {
+    // Somebody else is already performing automatic cleanup.
+    return;
+  }
+
+  // Intentionally not acquiring content locks here to avoid write contention
+  // since precision is not important. It doesn't matter if some compilation
+  // sneaks in a new result during our calculation - if maximum cache size
+  // becomes exceeded it will be taken care of the next time instead.
+  auto evaluation = evaluate_cleanup();
+  if (!evaluation) {
+    // No cleanup needed.
+    return;
+  }
+
+  auto_cleanup_lock.make_long_lived(lock_manager);
+
+  if (!has_consistent_counters(evaluation->l1_counters)) {
+    LOG("Recounting {} due to inconsistent counters", evaluation->l1_path);
+    recount_level_1_dir(lock_manager, evaluation->l1_index);
+    evaluation->l1_counters = get_stats_file(evaluation->l1_index).read();
+  }
+
+  uint8_t largest_level_2_index =
+    get_largest_level_2_index(evaluation->l1_counters);
+
+  auto l2_content_lock =
+    get_level_2_content_lock(evaluation->l1_index, largest_level_2_index);
+  l2_content_lock.make_long_lived(lock_manager);
+  if (!l2_content_lock.acquire()) {
+    LOG("Failed to acquire content lock for {}/{}",
+        evaluation->l1_index,
+        largest_level_2_index);
+    return;
+  }
+
+  // Need to reread the counters again after acquiring the lock since another
+  // compilation may have modified the size since evaluation->l1_counters was
+  // read.
+  auto stats_file = get_stats_file(evaluation->l1_index);
+  auto counters = stats_file.read();
+  if (!has_consistent_counters(counters)) {
+    // The cache_size_kibibyte counters doesn't match the 16
+    // subdir_size_kibibyte_base+i counters. This should only happen if an older
+    // ccache version (before introduction of the subdir_size_kibibyte_base
+    // counters) has modified the cache size after the recount_level_1_dir call
+    // above. Bail out now and leave it to the next ccache invocation to clean
+    // up the inconsistency.
+    LOG("Inconsistent counters in {}, bailing out", evaluation->l1_path);
+    return;
+  }
+
+  // Since counting files and their sizes is costly, remove more than needed to
+  // amortize the cost. Trimming the directory down to 90% of the max size means
+  // that statistically about 20% of the directory content will be removed each
+  // automatic cleanup (since subdirectories will be between 90% and about 110%
+  // filled at steady state).
+  //
+  // We trim based on number of files instead of size. The main reason for this
+  // is to be more forgiving if there is one or a few large cache entries among
+  // many smaller. For example, say that there's a single 100 MB entry (maybe
+  // the result of a precompiled header) and lots of small 50 kB files as well.
+  // If the large file is the oldest in the subdirectory that is chosen for
+  // cleanup, only one file would be removed, thus wasting most of the effort of
+  // stat-ing all files. On the other hand, if the large file is the newest, all
+  // or a large portion of the other files would be removed on cleanup, thus in
+  // practice removing much newer entries than the oldest in other
+  // subdirectories. By doing cleanup based on the number of files, both example
+  // scenarios are improved.
+  const uint64_t target_files = 0.9 * evaluation->total_files / 256;
+
+  auto clean_dir_result = clean_dir(
+    get_subdir(evaluation->l1_index, largest_level_2_index), 0, target_files);
+
+  stats_file.update([&](auto& cs) {
+    const auto old_files =
+      cs.get_offsetted(Statistic::subdir_files_base, largest_level_2_index);
+    const auto old_size_kibibyte = cs.get_offsetted(
+      Statistic::subdir_size_kibibyte_base, largest_level_2_index);
+    const auto new_files = clean_dir_result.after.files;
+    const auto new_size_kibibyte = clean_dir_result.after.size / 1024;
+    const int64_t cleanups =
+      clean_dir_result.after.size != clean_dir_result.before.size ? 1 : 0;
+
+    cs.increment(Statistic::files_in_cache, new_files - old_files);
+    cs.increment(Statistic::cache_size_kibibyte,
+                 new_size_kibibyte - old_size_kibibyte);
+    cs.set_offsetted(
+      Statistic::subdir_files_base, largest_level_2_index, new_files);
+    cs.set_offsetted(Statistic::subdir_size_kibibyte_base,
+                     largest_level_2_index,
+                     new_size_kibibyte);
+    cs.increment(Statistic::cleanups_performed, cleanups);
+  });
+}
+
+void
+LocalStorage::do_clean_all(const ProgressReceiver& progress_receiver,
+                           uint64_t max_size,
+                           uint64_t max_files,
+                           std::optional<uint64_t> max_age,
+                           std::optional<std::string> namespace_)
+{
+  util::LongLivedLockFileManager lock_manager;
+
+  uint64_t current_size = 0;
+  uint64_t current_files = 0;
+  if (max_size > 0 || max_files > 0) {
+    for_each_cache_subdir([&](uint8_t i) {
+      auto counters = get_stats_file(i).read();
+      current_size += 1024 * counters.get(Statistic::cache_size_kibibyte);
+      current_files += counters.get(Statistic::files_in_cache);
+    });
+  }
+
+  for_each_cache_subdir(
+    progress_receiver, [&](uint8_t l1_index, const auto& l1_progress_receiver) {
+      auto acquired_locks =
+        acquire_all_level_2_content_locks(lock_manager, l1_index);
+      Level1Counters level_1_counters;
+
+      for_each_cache_subdir(
+        l1_progress_receiver,
+        [&](uint8_t l2_index, const ProgressReceiver& l2_progress_receiver) {
+          uint64_t level_2_max_size =
+            current_size > max_size ? max_size / 256 : 0;
+          uint64_t level_2_max_files =
+            current_files > max_files ? max_files / 256 : 0;
+          auto clean_dir_result = clean_dir(get_subdir(l1_index, l2_index),
+                                            level_2_max_size,
+                                            level_2_max_files,
+                                            max_age,
+                                            namespace_,
+                                            l2_progress_receiver);
+          uint64_t removed_size =
+            clean_dir_result.before.size - clean_dir_result.after.size;
+          uint64_t removed_files =
+            clean_dir_result.before.files - clean_dir_result.after.files;
+
+          // removed_size/remove_files should never be larger than
+          // current_size/current_files, but in case there's some error we
+          // certainly don't want to underflow, so better safe than sorry.
+          current_size -= std::min(removed_size, current_size);
+          current_files -= std::min(removed_files, current_files);
+
+          level_1_counters.level_2_counters[l2_index] = clean_dir_result.after;
+          if (clean_dir_result.after.files != clean_dir_result.before.files) {
+            ++level_1_counters.cleanups;
+          }
+
+          // Fix erroneous files/size counters for raw files in L2 stats files.
+          // See also comments in finalize().
+          get_stats_file(l1_index, l2_index)
+            .update(
+              [](auto& cs) {
+                cs.set(Statistic::cache_size_kibibyte, 0);
+                cs.set(Statistic::files_in_cache, 0);
+              },
+              StatsFile::OnlyIfChanged::yes);
+        });
+
+      set_counters(get_stats_file(l1_index), level_1_counters);
+    });
+}
+
+std::optional<LocalStorage::EvaluateCleanupResult>
+LocalStorage::evaluate_cleanup()
+{
+  // We trust that the L1 size and files counters are correct, but the L2 size
+  // and files counters may be inconsistent if older ccache versions have been
+  // used. If all L2 counters are consistent, we choose the L1 directory with
+  // the largest L2 directory, otherwise we just choose the largest L1 directory
+  // since we can't trust the L2 counters.
+
+  std::vector<StatisticsCounters> counters;
+  counters.reserve(16);
+  for_each_cache_subdir([&](uint8_t l1_index) {
+    counters.emplace_back(get_stats_file(l1_index).read());
+  });
+  ASSERT(counters.size() == 16);
+
+  uint64_t largest_l1_dir_files = 0;
+  uint64_t largest_l2_dir_files = 0;
+  uint8_t largest_l1_dir = 0;
+  uint8_t l1_dir_with_largest_l2 = 0;
+  uint8_t largest_l2_dir = 0;
+  bool l2_counters_consistent = true;
+  uint64_t total_files = 0;
+  uint64_t total_size = 0;
+  for_each_cache_subdir([&](uint8_t i) {
+    auto l1_files = counters[i].get(Statistic::files_in_cache);
+    auto l1_size = 1024 * counters[i].get(Statistic::cache_size_kibibyte);
+    total_files += l1_files;
+    total_size += l1_size;
+    if (l1_files > largest_l1_dir_files) {
+      largest_l1_dir_files = l1_files;
+      largest_l1_dir = i;
+    }
+
+    if (l2_counters_consistent && has_consistent_counters(counters[i])) {
+      for_each_cache_subdir([&](uint8_t j) {
+        auto l2_files =
+          counters[i].get_offsetted(Statistic::subdir_files_base, j);
+        if (l2_files > largest_l2_dir_files) {
+          largest_l2_dir_files = l2_files;
+          l1_dir_with_largest_l2 = i;
+          largest_l2_dir = j;
+        }
+      });
+    } else {
+      l2_counters_consistent = false;
+    }
+  });
+
+  std::string max_size_str =
+    m_config.max_size() > 0
+      ? FMT(", max size {}",
+            util::format_human_readable_size(m_config.max_size(),
+                                             m_config.size_unit_prefix_type()))
+      : "";
+  std::string max_files_str =
+    m_config.max_files() > 0 ? FMT(", max files {}", m_config.max_files()) : "";
+  std::string info_str = FMT("size {}, files {}{}{}",
+                             util::format_human_readable_size(
+                               total_size, m_config.size_unit_prefix_type()),
+                             total_files,
+                             max_size_str,
+                             max_files_str);
+  if ((m_config.max_size() == 0 || total_size <= m_config.max_size())
+      && (m_config.max_files() == 0 || total_files <= m_config.max_files())) {
+    LOG("No automatic cleanup needed ({})", info_str);
+    return std::nullopt;
+  }
+
+  LOG("Need to clean up local cache ({})", info_str);
+
+  uint8_t chosen_l1_dir =
+    l2_counters_consistent ? l1_dir_with_largest_l2 : largest_l1_dir;
+  auto largest_level_1_dir_path = get_subdir(chosen_l1_dir);
+  LOG("Choosing {} for cleanup (counters {}, files {}{})",
+      largest_level_1_dir_path,
+      has_consistent_counters(counters[chosen_l1_dir]) ? "consistent"
+                                                       : "inconsistent",
+      largest_l1_dir_files,
+      l2_counters_consistent
+        ? FMT(", subdir {:x} files {}", largest_l2_dir, largest_l2_dir_files)
+        : std::string());
+
+  return EvaluateCleanupResult{chosen_l1_dir,
+                               largest_level_1_dir_path,
+                               counters[chosen_l1_dir],
+                               total_files};
+}
+
+std::vector<util::LockFile>
+LocalStorage::acquire_all_level_2_content_locks(
+  util::LongLivedLockFileManager& lock_manager, uint8_t l1_index)
+{
+  std::vector<util::LockFile> locks;
+
+  for_each_cache_subdir([&](uint8_t l2_index) {
+    auto lock = get_level_2_content_lock(l1_index, l2_index);
+    lock.make_long_lived(lock_manager);
+
+    // Not much to do on failure except treating the lock as acquired.
+    (void)lock.acquire();
+
+    locks.push_back(std::move(lock));
+  });
+
+  return locks;
+}
+
 void
 LocalStorage::clean_internal_tempdir()
 {
@@ -407,70 +1332,6 @@ LocalStorage::clean_internal_tempdir()
   util::write_file(cleaned_stamp, "");
 }
 
-std::optional<core::StatisticsCounters>
-LocalStorage::update_stats_and_maybe_move_cache_file(
-  const Digest& key,
-  const std::string& current_path,
-  const core::StatisticsCounters& counter_updates,
-  const core::CacheEntryType type)
-{
-  if (counter_updates.all_zero()) {
-    return std::nullopt;
-  }
-
-  // Use stats file in the level one subdirectory for cache bookkeeping counters
-  // since cleanup is performed on level one. Use stats file in the level two
-  // subdirectory for other counters to reduce lock contention.
-  const bool use_stats_on_level_1 =
-    counter_updates.get(Statistic::cache_size_kibibyte) != 0
-    || counter_updates.get(Statistic::files_in_cache) != 0;
-  std::string level_string = FMT("{:x}", key.bytes()[0] >> 4);
-  if (!use_stats_on_level_1) {
-    level_string += FMT("/{:x}", key.bytes()[0] & 0xF);
-  }
-
-  const auto stats_file =
-    FMT("{}/{}/stats", m_config.cache_dir(), level_string);
-  auto counters = StatsFile(stats_file).update([&counter_updates](auto& cs) {
-    cs.increment(counter_updates);
-  });
-  if (!counters) {
-    return std::nullopt;
-  }
-
-  if (use_stats_on_level_1) {
-    // Only consider moving the cache file to another level when we have read
-    // the level 1 stats file since it's only then we know the proper
-    // files_in_cache value.
-    const auto wanted_level =
-      calculate_wanted_cache_level(counters->get(Statistic::files_in_cache));
-    const auto wanted_path =
-      get_path_in_cache(wanted_level, key.to_string() + suffix_from_type(type));
-    if (current_path != wanted_path) {
-      Util::ensure_dir_exists(Util::dir_name(wanted_path));
-      LOG("Moving {} to {}", current_path, wanted_path);
-      try {
-        Util::rename(current_path, wanted_path);
-      } catch (const core::Error&) {
-        // Two ccache processes may move the file at the same time, so failure
-        // to rename is OK.
-      }
-      for (const auto& raw_file : m_added_raw_files) {
-        try {
-          Util::rename(raw_file,
-                       FMT("{}/{}",
-                           Util::dir_name(wanted_path),
-                           Util::base_name(raw_file)));
-        } catch (const core::Error&) {
-          // Two ccache processes may move the file at the same time, so failure
-          // to rename is OK.
-        }
-      }
-    }
-  }
-  return counters;
-}
-
 std::string
 LocalStorage::get_path_in_cache(const uint8_t level,
                                 const std::string_view name) const
@@ -493,4 +1354,31 @@ LocalStorage::get_path_in_cache(const uint8_t level,
   return path;
 }
 
+std::string
+LocalStorage::get_lock_path(const std::string& name) const
+{
+  auto path = FMT("{}/lock/{}", m_config.cache_dir(), name);
+  Util::ensure_dir_exists(Util::dir_name(path));
+  return path;
+}
+
+util::LockFile
+LocalStorage::get_auto_cleanup_lock() const
+{
+  return util::LockFile(get_lock_path("auto_cleanup"));
+}
+
+util::LockFile
+LocalStorage::get_level_2_content_lock(const Digest& key) const
+{
+  return get_level_2_content_lock(key.bytes()[0] >> 4, key.bytes()[0] & 0xF);
+}
+
+util::LockFile
+LocalStorage::get_level_2_content_lock(uint8_t l1_index, uint8_t l2_index) const
+{
+  return util::LockFile(
+    get_lock_path(FMT("subdir_{:x}{:x}", l1_index, l2_index)));
+}
+
 } // namespace storage::local
index 6251b3e..1e904ca 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 #include <core/Result.hpp>
 #include <core/StatisticsCounters.hpp>
 #include <core/types.hpp>
+#include <storage/local/StatsFile.hpp>
 #include <storage/local/util.hpp>
 #include <storage/types.hpp>
 #include <util/Bytes.hpp>
+#include <util/LockFile.hpp>
 #include <util/TimePoint.hpp>
 
 #include <third_party/nonstd/span.hpp>
 
 #include <cstdint>
 #include <optional>
+#include <string_view>
 #include <vector>
 
 class Config;
 
-namespace storage {
-namespace local {
+namespace storage::local {
 
 struct CompressionStatistics
 {
-  uint64_t compr_size;
+  // Storage that would be needed to store the content of compressible entries
+  // uncompressed (without headers), rounded up to disk blocks.
   uint64_t content_size;
-  uint64_t incompr_size;
-  uint64_t on_disk_size;
+  // Actual size of compressible entries (including headers), rounded up to disk
+  // blocks.
+  uint64_t actual_size;
+  // Actual size of incompressible entries, rounded up to disk blocks.
+  uint64_t incompressible_size;
 };
 
+enum class FileType { result, manifest, raw, unknown };
+
+FileType file_type_from_path(std::string_view path);
+
 class LocalStorage
 {
 public:
@@ -104,28 +114,18 @@ public:
   get_compression_statistics(const ProgressReceiver& progress_receiver) const;
 
   void recompress(std::optional<int8_t> level,
+                  uint32_t threads,
                   const ProgressReceiver& progress_receiver);
 
 private:
   const Config& m_config;
 
-  // Main statistics updates (result statistics and size/count change for result
-  // file) which get written into the statistics file belonging to the result
-  // file.
-  core::StatisticsCounters m_result_counter_updates;
-
-  // Statistics updates (only for manifest size/count change) which get written
-  // into the statistics file belonging to the manifest.
-  core::StatisticsCounters m_manifest_counter_updates;
-
-  // The manifest and result keys and paths are stored by put() so that
-  // finalize() can use them to move the files in place.
-  std::optional<Digest> m_manifest_key;
-  std::optional<Digest> m_result_key;
-  std::string m_manifest_path;
-  std::string m_result_path;
+  // Statistics updates (excluding size/count changes) that will get written to
+  // a statistics file in the finalize method.
+  core::StatisticsCounters m_counter_updates;
 
   std::vector<std::string> m_added_raw_files;
+  bool m_stored_data = false;
 
   struct LookUpCacheFileResult
   {
@@ -137,26 +137,65 @@ private:
   LookUpCacheFileResult look_up_cache_file(const Digest& key,
                                            core::CacheEntryType type) const;
 
-  void clean_internal_tempdir();
+  std::string get_subdir(uint8_t l1_index) const;
+  std::string get_subdir(uint8_t l1_index, uint8_t l2_index) const;
+
+  StatsFile get_stats_file(uint8_t l1_index) const;
+  StatsFile get_stats_file(uint8_t l1_index, uint8_t l2_index) const;
+
+  void move_to_wanted_cache_level(const core::StatisticsCounters& counters,
+                                  const Digest& key,
+                                  core::CacheEntryType type,
+                                  const std::string& cache_file_path);
+
+  void recount_level_1_dir(util::LongLivedLockFileManager& lock_manager,
+                           uint8_t l1_index);
+
+  std::optional<core::StatisticsCounters> increment_files_and_size_counters(
+    uint8_t l1_index, uint8_t l2_index, int64_t files, int64_t size_kibibyte);
+  std::optional<core::StatisticsCounters> increment_files_and_size_counters(
+    const Digest& key, int64_t files, int64_t size_kibibyte);
+
+  void perform_automatic_cleanup();
 
-  std::optional<core::StatisticsCounters>
-  update_stats_and_maybe_move_cache_file(
-    const Digest& key,
-    const std::string& current_path,
-    const core::StatisticsCounters& counter_updates,
-    core::CacheEntryType type);
+  void do_clean_all(const ProgressReceiver& progress_receiver,
+                    uint64_t max_size,
+                    uint64_t max_files,
+                    std::optional<uint64_t> max_age,
+                    std::optional<std::string> namespace_);
+
+  struct EvaluateCleanupResult
+  {
+    uint8_t l1_index;
+    std::string l1_path;
+    core::StatisticsCounters l1_counters;
+    uint64_t total_files;
+  };
+
+  std::optional<EvaluateCleanupResult> evaluate_cleanup();
+
+  std::vector<util::LockFile> acquire_all_level_2_content_locks(
+    util::LongLivedLockFileManager& lock_manager, uint8_t l1_index);
+
+  void clean_internal_tempdir();
 
   // Join the cache directory, a '/' and `name` into a single path and return
   // it. Additionally, `level` single-character, '/'-separated subpaths are
   // split from the beginning of `name` before joining them all.
   std::string get_path_in_cache(uint8_t level, std::string_view name) const;
 
-  static void clean_dir(const std::string& subdir,
-                        uint64_t max_size,
-                        uint64_t max_files,
-                        std::optional<uint64_t> max_age,
-                        std::optional<std::string> namespace_,
-                        const ProgressReceiver& progress_receiver);
+  std::string get_lock_path(const std::string& name) const;
+
+  util::LockFile get_auto_cleanup_lock() const;
+
+  // A level 2 content lock grants exclusive access to a level 2 directory in
+  // the cache. It must be acquired before adding, removing or recounting files
+  // in the directory (including any subdirectories). However, the lock does not
+  // have to be acquired to update a level 2 stats file since level 2 content
+  // size and file count are stored in the parent (level 1) stats file.
+  util::LockFile get_level_2_content_lock(const Digest& key) const;
+  util::LockFile get_level_2_content_lock(uint8_t l1_index,
+                                          uint8_t l2_index) const;
 };
 
 // --- Inline implementations ---
@@ -164,8 +203,7 @@ private:
 inline const core::StatisticsCounters&
 LocalStorage::get_statistics_updates() const
 {
-  return m_result_counter_updates;
+  return m_counter_updates;
 }
 
-} // namespace local
-} // namespace storage
+} // namespace storage::local
diff --git a/src/storage/local/LocalStorage_cleanup.cpp b/src/storage/local/LocalStorage_cleanup.cpp
deleted file mode 100644 (file)
index 3039e83..0000000
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright (C) 2002-2006 Andrew Tridgell
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-#include "LocalStorage.hpp"
-
-#include <Config.hpp>
-#include <Context.hpp>
-#include <File.hpp>
-#include <Logging.hpp>
-#include <TemporaryFile.hpp>
-#include <Util.hpp>
-#include <core/CacheEntry.hpp>
-#include <core/exceptions.hpp>
-#include <fmtmacros.hpp>
-#include <storage/local/CacheFile.hpp>
-#include <storage/local/StatsFile.hpp>
-#include <storage/local/util.hpp>
-#include <util/file.hpp>
-#include <util/string.hpp>
-
-#ifdef INODE_CACHE_SUPPORTED
-#  include <InodeCache.hpp>
-#endif
-
-#include <algorithm>
-
-using core::Statistic;
-
-namespace storage::local {
-
-static void
-delete_file(const std::string& path,
-            const uint64_t size,
-            uint64_t* cache_size,
-            uint64_t* files_in_cache)
-{
-  const bool deleted = Util::unlink_safe(path, Util::UnlinkLog::ignore_failure);
-  if (!deleted && errno != ENOENT && errno != ESTALE) {
-    LOG("Failed to unlink {} ({})", path, strerror(errno));
-  } else if (cache_size && files_in_cache) {
-    // The counters are intentionally subtracted even if there was no file to
-    // delete since the final cache size calculation will be incorrect if they
-    // aren't. (This can happen when there are several parallel ongoing
-    // cleanups of the same directory.)
-    *cache_size -= size;
-    --*files_in_cache;
-  }
-}
-
-static void
-update_counters(const std::string& dir,
-                const uint64_t files_in_cache,
-                const uint64_t cache_size,
-                const bool cleanup_performed)
-{
-  const std::string stats_file = dir + "/stats";
-  StatsFile(stats_file).update([=](auto& cs) {
-    if (cleanup_performed) {
-      cs.increment(Statistic::cleanups_performed);
-    }
-    cs.set(Statistic::files_in_cache, files_in_cache);
-    cs.set(Statistic::cache_size_kibibyte, cache_size / 1024);
-  });
-}
-
-void
-LocalStorage::evict(const ProgressReceiver& progress_receiver,
-                    std::optional<uint64_t> max_age,
-                    std::optional<std::string> namespace_)
-{
-  for_each_level_1_subdir(
-    m_config.cache_dir(),
-    [&](const std::string& subdir,
-        const ProgressReceiver& sub_progress_receiver) {
-      clean_dir(subdir, 0, 0, max_age, namespace_, sub_progress_receiver);
-    },
-    progress_receiver);
-}
-
-// Clean up one cache subdirectory.
-void
-LocalStorage::clean_dir(const std::string& subdir,
-                        const uint64_t max_size,
-                        const uint64_t max_files,
-                        const std::optional<uint64_t> max_age,
-                        const std::optional<std::string> namespace_,
-                        const ProgressReceiver& progress_receiver)
-{
-  LOG("Cleaning up cache directory {}", subdir);
-
-  std::vector<CacheFile> files = get_level_1_files(
-    subdir, [&](double progress) { progress_receiver(progress / 3); });
-
-  uint64_t cache_size = 0;
-  uint64_t files_in_cache = 0;
-  auto current_time = util::TimePoint::now();
-  std::unordered_map<std::string /*result_file*/,
-                     std::vector<std::string> /*associated_raw_files*/>
-    raw_files_map;
-
-  for (size_t i = 0; i < files.size();
-       ++i, progress_receiver(1.0 / 3 + 1.0 * i / files.size() / 3)) {
-    const auto& file = files[i];
-
-    if (!file.lstat().is_regular()) {
-      // Not a file or missing file.
-      continue;
-    }
-
-    // Delete any tmp files older than 1 hour right away.
-    if (file.lstat().mtime() + util::Duration(3600) < current_time
-        && TemporaryFile::is_tmp_file(file.path())) {
-      Util::unlink_tmp(file.path());
-      continue;
-    }
-
-    if (namespace_ && file.type() == CacheFile::Type::raw) {
-      const auto result_filename =
-        FMT("{}R", file.path().substr(0, file.path().length() - 2));
-      raw_files_map[result_filename].push_back(file.path());
-    }
-
-    cache_size += file.lstat().size_on_disk();
-    files_in_cache += 1;
-  }
-
-  // Sort according to modification time, oldest first.
-  std::sort(files.begin(), files.end(), [](const auto& f1, const auto& f2) {
-    return f1.lstat().mtime() < f2.lstat().mtime();
-  });
-
-  LOG("Before cleanup: {:.0f} KiB, {:.0f} files",
-      static_cast<double>(cache_size) / 1024,
-      static_cast<double>(files_in_cache));
-
-  bool cleaned = false;
-  for (size_t i = 0; i < files.size();
-       ++i, progress_receiver(2.0 / 3 + 1.0 * i / files.size() / 3)) {
-    const auto& file = files[i];
-
-    if (!file.lstat() || file.lstat().is_directory()) {
-      continue;
-    }
-
-    if ((max_size == 0 || cache_size <= max_size)
-        && (max_files == 0 || files_in_cache <= max_files)
-        && (!max_age
-            || file.lstat().mtime() > (current_time - util::Duration(*max_age)))
-        && (!namespace_ || max_age)) {
-      break;
-    }
-
-    if (namespace_) {
-      try {
-        core::CacheEntry::Header header(file.path());
-        if (header.namespace_ != *namespace_) {
-          continue;
-        }
-      } catch (core::Error&) {
-        // Failed to read header: ignore.
-        continue;
-      }
-
-      // For namespace eviction we need to remove raw files based on result
-      // filename since they don't have a header.
-      if (file.type() == CacheFile::Type::result) {
-        const auto entry = raw_files_map.find(file.path());
-        if (entry != raw_files_map.end()) {
-          for (const auto& raw_file : entry->second) {
-            delete_file(raw_file,
-                        Stat::lstat(raw_file).size_on_disk(),
-                        &cache_size,
-                        &files_in_cache);
-          }
-        }
-      }
-    }
-
-    if (util::ends_with(file.path(), ".stderr")) {
-      // In order to be nice to legacy ccache versions, make sure that the .o
-      // file is deleted before .stderr, because if the ccache process gets
-      // killed after deleting the .stderr but before deleting the .o, the
-      // cached result will be inconsistent. (.stderr is the only file that is
-      // optional for legacy ccache versions; any other file missing from the
-      // cache will be detected.)
-      std::string o_file = file.path().substr(0, file.path().size() - 6) + "o";
-
-      // Don't subtract this extra deletion from the cache size; that
-      // bookkeeping will be done when the loop reaches the .o file. If the
-      // loop doesn't reach the .o file since the target limits have been
-      // reached, the bookkeeping won't happen, but that small counter
-      // discrepancy won't do much harm and it will correct itself in the next
-      // cleanup.
-      delete_file(o_file, 0, nullptr, nullptr);
-    }
-
-    delete_file(
-      file.path(), file.lstat().size_on_disk(), &cache_size, &files_in_cache);
-    cleaned = true;
-  }
-
-  LOG("After cleanup: {:.0f} KiB, {:.0f} files",
-      static_cast<double>(cache_size) / 1024,
-      static_cast<double>(files_in_cache));
-
-  if (cleaned) {
-    LOG("Cleaned up cache directory {}", subdir);
-  }
-
-  update_counters(subdir, files_in_cache, cache_size, cleaned);
-}
-
-// Clean up all cache subdirectories.
-void
-LocalStorage::clean_all(const ProgressReceiver& progress_receiver)
-{
-  for_each_level_1_subdir(
-    m_config.cache_dir(),
-    [&](const std::string& subdir,
-        const ProgressReceiver& sub_progress_receiver) {
-      clean_dir(subdir,
-                m_config.max_size() / 16,
-                m_config.max_files() / 16,
-                std::nullopt,
-                std::nullopt,
-                sub_progress_receiver);
-    },
-    progress_receiver);
-}
-
-// Wipe one cache subdirectory.
-static void
-wipe_dir(const std::string& subdir, const ProgressReceiver& progress_receiver)
-{
-  LOG("Clearing out cache directory {}", subdir);
-
-  const std::vector<CacheFile> files = get_level_1_files(
-    subdir, [&](double progress) { progress_receiver(progress / 2); });
-
-  for (size_t i = 0; i < files.size(); ++i) {
-    Util::unlink_safe(files[i].path());
-    progress_receiver(0.5 + 0.5 * i / files.size());
-  }
-
-  const bool cleared = !files.empty();
-  if (cleared) {
-    LOG("Cleared out cache directory {}", subdir);
-  }
-  update_counters(subdir, 0, 0, cleared);
-}
-
-// Wipe all cached files in all subdirectories.
-void
-LocalStorage::wipe_all(const ProgressReceiver& progress_receiver)
-{
-  for_each_level_1_subdir(m_config.cache_dir(), wipe_dir, progress_receiver);
-}
-
-} // namespace storage::local
diff --git a/src/storage/local/LocalStorage_compress.cpp b/src/storage/local/LocalStorage_compress.cpp
deleted file mode 100644 (file)
index d983b5c..0000000
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-#include "LocalStorage.hpp"
-
-#include <AtomicFile.hpp>
-#include <Context.hpp>
-#include <File.hpp>
-#include <Logging.hpp>
-#include <TemporaryFile.hpp>
-#include <ThreadPool.hpp>
-#include <assertions.hpp>
-#include <core/CacheEntry.hpp>
-#include <core/Manifest.hpp>
-#include <core/Result.hpp>
-#include <core/exceptions.hpp>
-#include <core/wincompat.hpp>
-#include <fmtmacros.hpp>
-#include <storage/local/StatsFile.hpp>
-#include <util/expected.hpp>
-#include <util/file.hpp>
-#include <util/string.hpp>
-
-#include <third_party/fmt/core.h>
-
-#ifdef HAVE_UNISTD_H
-#  include <unistd.h>
-#endif
-
-#include <memory>
-#include <string>
-#include <thread>
-
-namespace storage::local {
-
-namespace {
-
-class RecompressionStatistics
-{
-public:
-  void update(uint64_t content_size,
-              uint64_t old_size,
-              uint64_t new_size,
-              uint64_t incompressible_size);
-  uint64_t content_size() const;
-  uint64_t old_size() const;
-  uint64_t new_size() const;
-  uint64_t incompressible_size() const;
-
-private:
-  mutable std::mutex m_mutex;
-  uint64_t m_content_size = 0;
-  uint64_t m_old_size = 0;
-  uint64_t m_new_size = 0;
-  uint64_t m_incompressible_size = 0;
-};
-
-void
-RecompressionStatistics::update(const uint64_t content_size,
-                                const uint64_t old_size,
-                                const uint64_t new_size,
-                                const uint64_t incompressible_size)
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  m_incompressible_size += incompressible_size;
-  m_content_size += content_size;
-  m_old_size += old_size;
-  m_new_size += new_size;
-}
-
-uint64_t
-RecompressionStatistics::content_size() const
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  return m_content_size;
-}
-
-uint64_t
-RecompressionStatistics::old_size() const
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  return m_old_size;
-}
-
-uint64_t
-RecompressionStatistics::new_size() const
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  return m_new_size;
-}
-
-uint64_t
-RecompressionStatistics::incompressible_size() const
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  return m_incompressible_size;
-}
-
-} // namespace
-
-static void
-recompress_file(RecompressionStatistics& statistics,
-                const std::string& stats_file,
-                const CacheFile& cache_file,
-                const std::optional<int8_t> level)
-{
-  core::CacheEntry::Header header(cache_file.path());
-
-  const int8_t wanted_level =
-    level ? (*level == 0 ? core::CacheEntry::default_compression_level : *level)
-          : 0;
-  const auto old_stat = Stat::stat(cache_file.path(), Stat::OnError::log);
-
-  if (header.compression_level == wanted_level) {
-    statistics.update(header.entry_size, old_stat.size(), old_stat.size(), 0);
-    return;
-  }
-
-  const auto cache_file_data = util::value_or_throw<core::Error>(
-    util::read_file<util::Bytes>(cache_file.path()),
-    FMT("Failed to read {}: ", cache_file.path()));
-  core::CacheEntry cache_entry(cache_file_data);
-  cache_entry.verify_checksum();
-
-  header.entry_format_version = core::CacheEntry::k_format_version;
-  header.compression_type =
-    level ? core::CompressionType::zstd : core::CompressionType::none;
-  header.compression_level = wanted_level;
-
-  AtomicFile new_cache_file(cache_file.path(), AtomicFile::Mode::binary);
-  new_cache_file.write(
-    core::CacheEntry::serialize(header, cache_entry.payload()));
-  new_cache_file.commit();
-
-  // Restore mtime/atime to keep cache LRU cleanup working as expected:
-  util::set_timestamps(cache_file.path(), old_stat.mtime(), old_stat.atime());
-
-  const auto new_stat = Stat::stat(cache_file.path(), Stat::OnError::log);
-  StatsFile(stats_file).update([=](auto& cs) {
-    cs.increment(core::Statistic::cache_size_kibibyte,
-                 Util::size_change_kibibyte(old_stat, new_stat));
-  });
-  statistics.update(header.entry_size, old_stat.size(), new_stat.size(), 0);
-}
-
-CompressionStatistics
-LocalStorage::get_compression_statistics(
-  const ProgressReceiver& progress_receiver) const
-{
-  CompressionStatistics cs{};
-
-  for_each_level_1_subdir(
-    m_config.cache_dir(),
-    [&](const auto& subdir, const auto& sub_progress_receiver) {
-      const std::vector<CacheFile> files = get_level_1_files(
-        subdir, [&](double progress) { sub_progress_receiver(progress / 2); });
-
-      for (size_t i = 0; i < files.size(); ++i) {
-        const auto& cache_file = files[i];
-        cs.on_disk_size += cache_file.lstat().size_on_disk();
-
-        try {
-          core::CacheEntry::Header header(cache_file.path());
-          cs.compr_size += cache_file.lstat().size();
-          cs.content_size += header.entry_size;
-        } catch (core::Error&) {
-          cs.incompr_size += cache_file.lstat().size();
-        }
-
-        sub_progress_receiver(1.0 / 2 + 1.0 * i / files.size() / 2);
-      }
-    },
-    progress_receiver);
-
-  return cs;
-}
-
-void
-LocalStorage::recompress(const std::optional<int8_t> level,
-                         const ProgressReceiver& progress_receiver)
-{
-  const size_t threads = std::thread::hardware_concurrency();
-  const size_t read_ahead = 2 * threads;
-  ThreadPool thread_pool(threads, read_ahead);
-  RecompressionStatistics statistics;
-
-  for_each_level_1_subdir(
-    m_config.cache_dir(),
-    [&](const auto& subdir, const auto& sub_progress_receiver) {
-      std::vector<CacheFile> files =
-        get_level_1_files(subdir, [&](double progress) {
-          sub_progress_receiver(0.1 * progress);
-        });
-
-      auto stats_file = subdir + "/stats";
-
-      for (size_t i = 0; i < files.size(); ++i) {
-        const auto& file = files[i];
-
-        if (file.type() != CacheFile::Type::unknown) {
-          thread_pool.enqueue([&statistics, stats_file, file, level] {
-            try {
-              recompress_file(statistics, stats_file, file, level);
-            } catch (core::Error&) {
-              // Ignore for now.
-            }
-          });
-        } else if (!TemporaryFile::is_tmp_file(file.path())) {
-          statistics.update(0, 0, 0, file.lstat().size());
-        }
-
-        sub_progress_receiver(0.1 + 0.9 * i / files.size());
-      }
-
-      if (util::ends_with(subdir, "f")) {
-        // Wait here instead of after for_each_level_1_subdir to avoid
-        // updating the progress bar to 100% before all work is done.
-        thread_pool.shut_down();
-      }
-    },
-    progress_receiver);
-
-  // In case there was no f subdir, shut down the thread pool now.
-  thread_pool.shut_down();
-
-  if (isatty(STDOUT_FILENO)) {
-    PRINT_RAW(stdout, "\n\n");
-  }
-
-  const double old_ratio =
-    statistics.old_size() > 0
-      ? static_cast<double>(statistics.content_size()) / statistics.old_size()
-      : 0.0;
-  const double old_savings =
-    old_ratio > 0.0 ? 100.0 - (100.0 / old_ratio) : 0.0;
-  const double new_ratio =
-    statistics.new_size() > 0
-      ? static_cast<double>(statistics.content_size()) / statistics.new_size()
-      : 0.0;
-  const double new_savings =
-    new_ratio > 0.0 ? 100.0 - (100.0 / new_ratio) : 0.0;
-  const int64_t size_difference = static_cast<int64_t>(statistics.new_size())
-                                  - static_cast<int64_t>(statistics.old_size());
-
-  const std::string old_compr_size_str =
-    Util::format_human_readable_size(statistics.old_size());
-  const std::string new_compr_size_str =
-    Util::format_human_readable_size(statistics.new_size());
-  const std::string content_size_str =
-    Util::format_human_readable_size(statistics.content_size());
-  const std::string incompr_size_str =
-    Util::format_human_readable_size(statistics.incompressible_size());
-  const std::string size_difference_str =
-    FMT("{}{}",
-        size_difference < 0 ? "-" : (size_difference > 0 ? "+" : " "),
-        Util::format_human_readable_size(
-          size_difference < 0 ? -size_difference : size_difference));
-
-  PRINT(stdout, "Original data:         {:>8s}\n", content_size_str);
-  PRINT(stdout,
-        "Old compressed data:   {:>8s} ({:.1f}% of original size)\n",
-        old_compr_size_str,
-        100.0 - old_savings);
-  PRINT(stdout,
-        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
-        old_ratio,
-        old_savings);
-  PRINT(stdout,
-        "New compressed data:   {:>8s} ({:.1f}% of original size)\n",
-        new_compr_size_str,
-        100.0 - new_savings);
-  PRINT(stdout,
-        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
-        new_ratio,
-        new_savings);
-  PRINT(stdout, "Size change:          {:>9s}\n", size_difference_str);
-}
-
-} // namespace storage::local
diff --git a/src/storage/local/LocalStorage_statistics.cpp b/src/storage/local/LocalStorage_statistics.cpp
deleted file mode 100644 (file)
index b801313..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-#include "LocalStorage.hpp"
-
-#include <Config.hpp>
-#include <core/Statistics.hpp>
-#include <fmtmacros.hpp>
-#include <storage/local/StatsFile.hpp>
-
-#include <algorithm>
-
-namespace storage::local {
-
-static void
-for_each_level_1_and_2_stats_file(
-  const std::string& cache_dir,
-  const std::function<void(const std::string& path)> function)
-{
-  for (size_t level_1 = 0; level_1 <= 0xF; ++level_1) {
-    function(FMT("{}/{:x}/stats", cache_dir, level_1));
-    for (size_t level_2 = 0; level_2 <= 0xF; ++level_2) {
-      function(FMT("{}/{:x}/{:x}/stats", cache_dir, level_1, level_2));
-    }
-  }
-}
-
-// Zero all statistics counters except those tracking cache size and number of
-// files in the cache.
-void
-LocalStorage::zero_all_statistics()
-{
-  const auto now = util::TimePoint::now();
-  const auto zeroable_fields = core::Statistics::get_zeroable_fields();
-
-  for_each_level_1_and_2_stats_file(
-    m_config.cache_dir(), [=](const std::string& path) {
-      StatsFile(path).update([=](auto& cs) {
-        for (const auto statistic : zeroable_fields) {
-          cs.set(statistic, 0);
-        }
-        cs.set(core::Statistic::stats_zeroed_timestamp, now.sec());
-      });
-    });
-}
-
-// Get statistics and last time of update for the whole local storage cache.
-std::pair<core::StatisticsCounters, util::TimePoint>
-LocalStorage::get_all_statistics() const
-{
-  core::StatisticsCounters counters;
-  uint64_t zero_timestamp = 0;
-  util::TimePoint last_updated;
-
-  // Add up the stats in each directory.
-  for_each_level_1_and_2_stats_file(
-    m_config.cache_dir(), [&](const auto& path) {
-      counters.set(core::Statistic::stats_zeroed_timestamp, 0); // Don't add
-      counters.increment(StatsFile(path).read());
-      zero_timestamp = std::max(
-        counters.get(core::Statistic::stats_zeroed_timestamp), zero_timestamp);
-      last_updated = std::max(last_updated, Stat::stat(path).mtime());
-    });
-
-  counters.set(core::Statistic::stats_zeroed_timestamp, zero_timestamp);
-  return std::make_pair(counters, last_updated);
-}
-
-} // namespace storage::local
index 4332d80..0c3bec0 100644 (file)
@@ -60,29 +60,32 @@ StatsFile::read() const
 
 std::optional<core::StatisticsCounters>
 StatsFile::update(
-  std::function<void(core::StatisticsCounters& counters)> function) const
+  std::function<void(core::StatisticsCounters& counters)> function,
+  OnlyIfChanged only_if_changed) const
 {
-  util::ShortLivedLockFile lock_file(m_path);
-  util::LockFileGuard lock(lock_file);
-  if (!lock.acquired()) {
+  util::LockFile lock(m_path);
+  if (!lock.acquire()) {
     LOG("Failed to acquire lock for {}", m_path);
     return std::nullopt;
   }
 
   auto counters = read();
+  const auto orig_counters = counters;
   function(counters);
 
-  AtomicFile file(m_path, AtomicFile::Mode::text);
-  for (size_t i = 0; i < counters.size(); ++i) {
-    file.write(FMT("{}\n", counters.get_raw(i)));
-  }
-  try {
-    file.commit();
-  } catch (const core::Error& e) {
-    // Make failure to write a stats file a soft error since it's not important
-    // enough to fail whole the process and also because it is called in the
-    // Context destructor.
-    LOG("Error: {}", e.what());
+  if (only_if_changed == OnlyIfChanged::no || counters != orig_counters) {
+    AtomicFile file(m_path, AtomicFile::Mode::text);
+    for (size_t i = 0; i < counters.size(); ++i) {
+      file.write(FMT("{}\n", counters.get_raw(i)));
+    }
+    try {
+      file.commit();
+    } catch (const core::Error& e) {
+      // Make failure to write a stats file a soft error since it's not
+      // important enough to fail whole the process and also because it is
+      // called in the Context destructor.
+      LOG("Error: {}", e.what());
+    }
   }
 
   return counters;
index 2ac4026..cab947c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -29,20 +29,23 @@ namespace storage::local {
 class StatsFile
 {
 public:
-  StatsFile(const std::string& path);
+  explicit StatsFile(const std::string& path);
 
   // Read counters. No lock is acquired. If the file doesn't exist all returned
   // counters will be zero.
   core::StatisticsCounters read() const;
 
+  enum class OnlyIfChanged { no, yes };
+
   // Acquire a lock, read counters, call `function` with the counters, write the
   // counters and release the lock. Returns the resulting counters or nullopt on
   // error (e.g. if the lock could not be acquired).
   std::optional<core::StatisticsCounters>
-    update(std::function<void(core::StatisticsCounters& counters)>) const;
+  update(std::function<void(core::StatisticsCounters& counters)>,
+         OnlyIfChanged only_if_changed = OnlyIfChanged::no) const;
 
 private:
-  const std::string m_path;
+  std::string m_path;
 };
 
 } // namespace storage::local
index 1035925..f994dae 100644 (file)
 namespace storage::local {
 
 void
-for_each_level_1_subdir(const std::string& cache_dir,
-                        const SubdirVisitor& visitor,
-                        const ProgressReceiver& progress_receiver)
+for_each_cache_subdir(const SubdirVisitor& visitor)
 {
-  for (int i = 0; i <= 0xF; i++) {
-    double progress = 1.0 * i / 16;
+  for (uint8_t i = 0; i < 16; ++i) {
+    visitor(i);
+  }
+}
+
+void
+for_each_cache_subdir(const ProgressReceiver& progress_receiver,
+                      const SubdirProgressVisitor& visitor)
+{
+  for (uint8_t i = 0; i < 16; ++i) {
+    double progress = i / 16.0;
     progress_receiver(progress);
-    std::string subdir_path = FMT("{}/{:x}", cache_dir, i);
-    visitor(subdir_path, [&](double inner_progress) {
+    visitor(i, [&](double inner_progress) {
       progress_receiver(progress + inner_progress / 16);
     });
   }
   progress_receiver(1.0);
 }
 
-std::vector<CacheFile>
-get_level_1_files(const std::string& dir,
-                  const ProgressReceiver& progress_receiver)
+void
+for_each_level_1_and_2_stats_file(
+  const std::string& cache_dir,
+  const std::function<void(const std::string& path)> function)
 {
-  std::vector<CacheFile> files;
+  for (size_t level_1 = 0; level_1 <= 0xF; ++level_1) {
+    function(FMT("{}/{:x}/stats", cache_dir, level_1));
+    for (size_t level_2 = 0; level_2 <= 0xF; ++level_2) {
+      function(FMT("{}/{:x}/{:x}/stats", cache_dir, level_1, level_2));
+    }
+  }
+}
+
+std::vector<Stat>
+get_cache_dir_files(const std::string& dir)
+{
+  std::vector<Stat> files;
 
   if (!Stat::stat(dir)) {
     return files;
   }
 
-  size_t level_2_directories = 0;
-
   Util::traverse(dir, [&](const std::string& path, bool is_dir) {
     auto name = Util::base_name(path);
     if (name == "CACHEDIR.TAG" || name == "stats"
@@ -60,15 +76,10 @@ get_level_1_files(const std::string& dir,
     }
 
     if (!is_dir) {
-      files.emplace_back(path);
-    } else if (path != dir
-               && path.find('/', dir.size() + 1) == std::string::npos) {
-      ++level_2_directories;
-      progress_receiver(level_2_directories / 16.0);
+      files.emplace_back(Stat::lstat(path));
     }
   });
 
-  progress_receiver(1.0);
   return files;
 }
 
index 8aa9b87..ed2ada4 100644 (file)
@@ -18,7 +18,7 @@
 
 #pragma once
 
-#include <storage/local/CacheFile.hpp>
+#include <Stat.hpp>
 
 #include <functional>
 #include <string>
 namespace storage::local {
 
 using ProgressReceiver = std::function<void(double progress)>;
-using SubdirVisitor = std::function<void(
-  const std::string& dir_path, const ProgressReceiver& progress_receiver)>;
+using SubdirVisitor = std::function<void(uint8_t subdir_index)>;
+using SubdirProgressVisitor = std::function<void(
+  uint8_t subdir_index, const ProgressReceiver& progress_receiver)>;
 
-// Call a function for each subdir (0-9a-f) in the cache.
-//
-// Parameters:
-// - cache_dir: Path to the cache directory.
-// - visitor: Function to call with directory path and progress_receiver as
-//   arguments.
-// - progress_receiver: Function that will be called for progress updates.
-void for_each_level_1_subdir(const std::string& cache_dir,
-                             const SubdirVisitor& visitor,
-                             const ProgressReceiver& progress_receiver);
-
-// Get a list of files in a level 1 subdirectory of the cache.
+// Call `visitor` for each subdirectory (0-9a-f) in `cache_dir`.
+void for_each_cache_subdir(const SubdirVisitor& visitor);
+void for_each_cache_subdir(const ProgressReceiver& progress_receiver,
+                           const SubdirProgressVisitor& visitor);
+
+void for_each_level_1_and_2_stats_file(
+  const std::string& cache_dir,
+  const std::function<void(const std::string& path)> function);
+
+// Get a list of files in a subdirectory of the cache.
 //
 // The function works under the assumption that directory entries with one
 // character names (except ".") are subdirectories and that there are no other
@@ -51,12 +50,6 @@ void for_each_level_1_subdir(const std::string& cache_dir,
 // - CACHEDIR.TAG
 // - stats
 // - .nfs* (temporary NFS files that may be left for open but deleted files).
-//
-// Parameters:
-// - dir: The directory to traverse recursively.
-// - progress_receiver: Function that will be called for progress updates.
-std::vector<CacheFile>
-get_level_1_files(const std::string& dir,
-                  const ProgressReceiver& progress_receiver);
+std::vector<Stat> get_cache_dir_files(const std::string& dir);
 
 } // namespace storage::local
index b1c8a33..a860732 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -71,6 +71,10 @@ FileStorageBackend::FileStorageBackend(const Params& params)
   const auto& host = params.url.host();
 #ifdef _WIN32
   m_dir = util::replace_all(params.url.path(), "/", "\\");
+  if (m_dir.length() >= 3 && m_dir[0] == '\\' && m_dir[2] == ':') {
+    // \X:\foo\bar -> X:\foo\bar according to RFC 8089 appendix E.2.
+    m_dir = m_dir.substr(1);
+  }
   if (!host.empty()) {
     m_dir = FMT("\\\\{}{}", host, m_dir);
   }
index 3240654..62fc535 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -20,6 +20,7 @@
 #include <Logging.hpp>
 #include <fmtmacros.hpp>
 #include <util/LockFile.hpp>
+#include <util/LongLivedLockFileManager.hpp>
 #include <util/string.hpp>
 
 #include <memory>
@@ -48,32 +49,29 @@ main(int argc, char** argv)
     return 1;
   }
 
-  using LockFilePtr = std::unique_ptr<util::LockFile>;
-  LockFilePtr lock_file;
-  lock_file = long_lived
-                ? LockFilePtr{std::make_unique<util::LongLivedLockFile>(path)}
-                : LockFilePtr{std::make_unique<util::ShortLivedLockFile>(path)};
-  const auto mode = blocking ? util::LockFileGuard::Mode::blocking
-                             : util::LockFileGuard::Mode::non_blocking;
-
-  PRINT(stdout, "{}\n", blocking ? "Acquiring" : "Trying to acquire");
+  util::LongLivedLockFileManager lock_manager;
+  util::LockFile lock(path);
   bool acquired = false;
-  {
-    util::LockFileGuard lock(*lock_file, mode);
-    acquired = lock.acquired();
-    if (acquired) {
-      PRINT_RAW(stdout, "Acquired\n");
-      PRINT(
-        stdout, "Sleeping {} second{}\n", *seconds, *seconds == 1 ? "" : "s");
-      std::this_thread::sleep_for(std::chrono::seconds{*seconds});
-    } else {
-      PRINT(stdout, "{} acquire\n", blocking ? "Failed to" : "Did not");
-    }
-    if (acquired) {
-      PRINT_RAW(stdout, "Releasing\n");
-    }
+  if (blocking) {
+    PRINT_RAW(stdout, "Acquiring\n");
+    acquired = lock.acquire();
+  } else {
+    PRINT_RAW(stdout, "Trying to acquire\n");
+    acquired = lock.try_acquire();
   }
-  if (acquired) {
-    PRINT_RAW(stdout, "Released\n");
+
+  if (!acquired) {
+    PRINT(stdout, "{} acquire\n", blocking ? "Failed to" : "Did not");
+    return 1;
+  }
+
+  PRINT_RAW(stdout, "Acquired\n");
+  if (long_lived) {
+    lock.make_long_lived(lock_manager);
   }
+  PRINT(stdout, "Sleeping {} second{}\n", *seconds, *seconds == 1 ? "" : "s");
+  std::this_thread::sleep_for(std::chrono::seconds{*seconds});
+  PRINT_RAW(stdout, "Releasing\n");
+  lock.release();
+  PRINT_RAW(stdout, "Released\n");
 }
index a30342d..1e30eb3 100644 (file)
@@ -27,7 +27,11 @@ function(add_source_if_enabled feature msvc_flags others_flags intrinsic)
 
   # First check if it's possible to use the assembler variant for the feature.
   string(TOUPPER "have_asm_${feature}" have_feature)
-  if(NOT DEFINED "${have_feature}" AND CMAKE_SIZEOF_VOID_P EQUAL 8)
+  if(NOT DEFINED "${have_feature}" AND CMAKE_SIZEOF_VOID_P EQUAL 8
+# Force intrinsic version for msbuild because of a bug in the cmake generator
+# or msbuild itself with masm flags.
+      AND NOT CMAKE_GENERATOR MATCHES "Visual Studio")
+
     if(NOT CMAKE_REQUIRED_QUIET)
       message(STATUS "Performing Test ${have_feature}")
     endif()
index 1239433..dc343f9 100644 (file)
@@ -246,7 +246,7 @@ INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values,
 
 // The wide helper function returns (writes out) an array of chaining values
 // and returns the length of that array. The number of chaining values returned
-// is the dyanmically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer,
+// is the dynamically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer,
 // if the input is shorter than that many chunks. The reason for maintaining a
 // wide array of chaining values going back up the tree, is to allow the
 // implementation to hash as many parents in parallel as possible.
@@ -254,7 +254,7 @@ INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values,
 // As a special case when the SIMD degree is 1, this function will still return
 // at least 2 outputs. This guarantees that this function doesn't perform the
 // root compression. (If it did, it would use the wrong flags, and also we
-// wouldn't be able to implement exendable ouput.) Note that this function is
+// wouldn't be able to implement exendable output.) Note that this function is
 // not used when the whole input is only 1 chunk long; that's a different
 // codepath.
 //
index 7caf9b4..aa4bfa6 100644 (file)
@@ -4,11 +4,33 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#if !defined(BLAKE3_API)
+# if defined(_WIN32) || defined(__CYGWIN__)
+#   if defined(BLAKE3_DLL)
+#     if defined(BLAKE3_DLL_EXPORTS)
+#       define BLAKE3_API __declspec(dllexport)
+#     else
+#       define BLAKE3_API __declspec(dllimport)
+#     endif
+#     define BLAKE3_PRIVATE
+#   else
+#     define BLAKE3_API
+#     define BLAKE3_PRIVATE
+#   endif
+# elif __GNUC__ >= 4
+#   define BLAKE3_API __attribute__((visibility("default")))
+#   define BLAKE3_PRIVATE __attribute__((visibility("hidden")))
+# else
+#   define BLAKE3_API
+#   define BLAKE3_PRIVATE
+# endif
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-#define BLAKE3_VERSION_STRING "1.3.1"
+#define BLAKE3_VERSION_STRING "1.4.0"
 #define BLAKE3_KEY_LEN 32
 #define BLAKE3_OUT_LEN 32
 #define BLAKE3_BLOCK_LEN 64
@@ -38,20 +60,20 @@ typedef struct {
   uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN];
 } blake3_hasher;
 
-const char *blake3_version(void);
-void blake3_hasher_init(blake3_hasher *self);
-void blake3_hasher_init_keyed(blake3_hasher *self,
-                              const uint8_t key[BLAKE3_KEY_LEN]);
-void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);
-void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
-                                       size_t context_len);
-void blake3_hasher_update(blake3_hasher *self, const void *input,
-                          size_t input_len);
-void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
-                            size_t out_len);
-void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
-                                 uint8_t *out, size_t out_len);
-void blake3_hasher_reset(blake3_hasher *self);
+BLAKE3_API const char *blake3_version(void);
+BLAKE3_API void blake3_hasher_init(blake3_hasher *self);
+BLAKE3_API void blake3_hasher_init_keyed(blake3_hasher *self,
+                                         const uint8_t key[BLAKE3_KEY_LEN]);
+BLAKE3_API void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);
+BLAKE3_API void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
+                                                  size_t context_len);
+BLAKE3_API void blake3_hasher_update(blake3_hasher *self, const void *input,
+                                     size_t input_len);
+BLAKE3_API void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
+                                       size_t out_len);
+BLAKE3_API void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
+                                            uint8_t *out, size_t out_len);
+BLAKE3_API void blake3_hasher_reset(blake3_hasher *self);
 
 #ifdef __cplusplus
 }
index e76aa1a..381e7c4 100644 (file)
@@ -167,7 +167,7 @@ INLINE void transpose_vecs(__m256i vecs[DEGREE]) {
   __m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]);
   __m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]);
 
-  // Interleave 64-bit lates. The low unpack is lanes 00/22 and the high is
+  // Interleave 64-bit lanes. The low unpack is lanes 00/22 and the high is
   // 11/33.
   __m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145);
   __m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145);
index bb58d2a..3d4be4a 100644 (file)
@@ -1784,7 +1784,7 @@ blake3_hash_many_avx2:
         vmovdqu xmmword ptr [rbx+0x10], xmm1
         jmp     4b
 
-.section .rodata
+.section .rdata
 .p2align  6
 ADD0:
         .long  0, 1, 2, 3, 4, 5, 6, 7
index 9c35b08..d6b1ae9 100644 (file)
@@ -429,7 +429,7 @@ INLINE void round_fn4(__m128i v[16], __m128i m[16], size_t r) {
 }
 
 INLINE void transpose_vecs_128(__m128i vecs[4]) {
-  // Interleave 32-bit lates. The low unpack is lanes 00/11 and the high is
+  // Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is
   // 22/33. Note that this doesn't split the vector into two lanes, as the
   // AVX2 counterparts do.
   __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);
@@ -684,7 +684,7 @@ INLINE void transpose_vecs_256(__m256i vecs[8]) {
   __m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]);
   __m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]);
 
-  // Interleave 64-bit lates. The low unpack is lanes 00/22 and the high is
+  // Interleave 64-bit lanes. The low unpack is lanes 00/22 and the high is
   // 11/33.
   __m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145);
   __m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145);
@@ -959,7 +959,7 @@ INLINE void transpose_vecs_512(__m512i vecs[16]) {
   __m512i op_0 = _mm512_unpacklo_epi32(vecs[14], vecs[15]);
   __m512i op_2 = _mm512_unpackhi_epi32(vecs[14], vecs[15]);
 
-  // Interleave 64-bit lates. The _0 unpack is lanes
+  // Interleave 64-bit lanes. The _0 unpack is lanes
   // 0/0/0/0/4/4/4/4/8/8/8/8/12/12/12/12, the _1 unpack is lanes
   // 1/1/1/1/5/5/5/5/9/9/9/9/13/13/13/13, the _2 unpack is lanes
   // 2/2/2/2/6/6/6/6/10/10/10/10/14/14/14/14, and the _3 unpack is lanes
@@ -1047,13 +1047,26 @@ INLINE void transpose_msg_vecs16(const uint8_t *const *inputs,
 INLINE void load_counters16(uint64_t counter, bool increment_counter,
                             __m512i *out_lo, __m512i *out_hi) {
   const __m512i mask = _mm512_set1_epi32(-(int32_t)increment_counter);
-  const __m512i add0 = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
-  const __m512i add1 = _mm512_and_si512(mask, add0);
-  __m512i l = _mm512_add_epi32(_mm512_set1_epi32((int32_t)counter), add1);
-  __mmask16 carry = _mm512_cmp_epu32_mask(l, add1, _MM_CMPINT_LT);
-  __m512i h = _mm512_mask_add_epi32(_mm512_set1_epi32((int32_t)(counter >> 32)), carry, _mm512_set1_epi32((int32_t)(counter >> 32)), _mm512_set1_epi32(1));
-  *out_lo = l;
-  *out_hi = h;
+  const __m512i deltas = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
+  const __m512i masked_deltas = _mm512_and_si512(deltas, mask);
+  const __m512i low_words = _mm512_add_epi32(
+    _mm512_set1_epi32((int32_t)counter),
+    masked_deltas);
+  // The carry bit is 1 if the high bit of the word was 1 before addition and is
+  // 0 after.
+  // NOTE: It would be a bit more natural to use _mm512_cmp_epu32_mask to
+  // compute the carry bits here, and originally we did, but that intrinsic is
+  // broken under GCC 5.4. See https://github.com/BLAKE3-team/BLAKE3/issues/271.
+  const __m512i carries = _mm512_srli_epi32(
+    _mm512_andnot_si512(
+        low_words, // 0 after (gets inverted by andnot)
+        _mm512_set1_epi32((int32_t)counter)), // and 1 before
+    31);
+  const __m512i high_words = _mm512_add_epi32(
+    _mm512_set1_epi32((int32_t)(counter >> 32)),
+    carries);
+  *out_lo = low_words;
+  *out_hi = high_words;
 }
 
 static
index e10b9f3..ba4fc5f 100644 (file)
@@ -2587,7 +2587,7 @@ blake3_compress_xof_avx512:
         add     rsp, 72
         ret
 
-.section .rodata
+.section .rdata
 .p2align  6
 INDEX0:
         .long    0,  1,  2,  3, 16, 17, 18, 19
index b498058..2ab0093 100644 (file)
 #elif defined(__GNUC__)
 #include <immintrin.h>
 #else
-#error "Unimplemented!"
+#undef IS_X86 /* Unimplemented! */
 #endif
 #endif
 
 #define MAYBE_UNUSED(x) (void)((x))
 
 #if defined(IS_X86)
-static uint64_t xgetbv() {
+static uint64_t xgetbv(void) {
 #if defined(_MSC_VER)
   return _xgetbv(0);
 #else
@@ -82,7 +82,7 @@ static /* Allow the variable to be controlled manually for testing */
 static
 #endif
     enum cpu_feature
-    get_cpu_features() {
+    get_cpu_features(void) {
 
   if (g_cpu_features != UNDEFINED) {
     return g_cpu_features;
@@ -101,7 +101,7 @@ static
     if (*edx & (1UL << 26))
       features |= SSE2;
 #endif
-    if (*ecx & (1UL << 0))
+    if (*ecx & (1UL << 9))
       features |= SSSE3;
     if (*ecx & (1UL << 19))
       features |= SSE41;
index cc5672f..3ba9ceb 100644 (file)
@@ -46,7 +46,6 @@ enum blake3_flags {
 #if defined(_MSC_VER)
 #include <intrin.h>
 #endif
-#include <immintrin.h>
 #endif
 
 #if !defined(BLAKE3_USE_NEON) 
@@ -88,7 +87,7 @@ static const uint8_t MSG_SCHEDULE[7][16] = {
 /* x is assumed to be nonzero.       */
 static unsigned int highest_one(uint64_t x) {
 #if defined(__GNUC__) || defined(__clang__)
-  return 63 ^ __builtin_clzll(x);
+  return 63 ^ (unsigned int)__builtin_clzll(x);
 #elif defined(_MSC_VER) && defined(IS_X86_64)
   unsigned long index;
   _BitScanReverse64(&index, x);
@@ -118,7 +117,7 @@ static unsigned int highest_one(uint64_t x) {
 // Count the number of 1 bits.
 INLINE unsigned int popcnt(uint64_t x) {
 #if defined(__GNUC__) || defined(__clang__)
-  return __builtin_popcountll(x);
+  return (unsigned int)__builtin_popcountll(x);
 #else
   unsigned int count = 0;
   while (x != 0) {
index f4449ac..691e1c6 100644 (file)
@@ -396,7 +396,7 @@ INLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) {
 }
 
 INLINE void transpose_vecs(__m128i vecs[DEGREE]) {
-  // Interleave 32-bit lates. The low unpack is lanes 00/11 and the high is
+  // Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is
   // 22/33. Note that this doesn't split the vector into two lanes, as the
   // AVX2 counterparts do.
   __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);
index 8852ba5..4facb50 100644 (file)
@@ -2301,7 +2301,7 @@ blake3_compress_xof_sse2:
         ret
 
 
-.section .rodata
+.section .rdata
 .p2align  6
 BLAKE3_IV:
         .long  0x6A09E667, 0xBB67AE85
index 87a8dae..4653a85 100644 (file)
@@ -390,7 +390,7 @@ INLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) {
 }
 
 INLINE void transpose_vecs(__m128i vecs[DEGREE]) {
-  // Interleave 32-bit lates. The low unpack is lanes 00/11 and the high is
+  // Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is
   // 22/33. Note that this doesn't split the vector into two lanes, as the
   // AVX2 counterparts do.
   __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);
index 60d0a40..02083f9 100644 (file)
@@ -2042,7 +2042,7 @@ blake3_compress_xof_sse41:
         ret
 
 
-.section .rodata
+.section .rdata
 .p2align  6
 BLAKE3_IV:
         .long  0x6A09E667, 0xBB67AE85
index aa2724c..5c754cd 100644 (file)
@@ -4,7 +4,7 @@
 //
 // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
 //
-// Copyright (c) 2016-2021 Viktor Kirilov
+// Copyright (c) 2016-2023 Viktor Kirilov
 //
 // Distributed under the MIT Software License
 // See accompanying file LICENSE.txt or copy at
@@ -48,7 +48,7 @@
 
 #define DOCTEST_VERSION_MAJOR 2
 #define DOCTEST_VERSION_MINOR 4
-#define DOCTEST_VERSION_PATCH 9
+#define DOCTEST_VERSION_PATCH 11
 
 // util we need here
 #define DOCTEST_TOSTR_IMPL(x) #x
     DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000)
 #endif // MSVC
 #endif // MSVC
-#if defined(__clang__) && defined(__clang_minor__)
+#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__)
 #define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__)
 #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) &&              \
         !defined(__INTEL_COMPILER)
 #define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
 #endif // GCC
+#if defined(__INTEL_COMPILER)
+#define DOCTEST_ICC DOCTEST_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)
+#endif // ICC
 
 #ifndef DOCTEST_MSVC
 #define DOCTEST_MSVC 0
 #ifndef DOCTEST_GCC
 #define DOCTEST_GCC 0
 #endif // DOCTEST_GCC
+#ifndef DOCTEST_ICC
+#define DOCTEST_ICC 0
+#endif // DOCTEST_ICC
 
 // =================================================================================================
 // == COMPILER WARNINGS HELPERS ====================================================================
 // =================================================================================================
 
-#if DOCTEST_CLANG
+#if DOCTEST_CLANG && !DOCTEST_ICC
 #define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
 #define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push")
 #define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w)
 // =================================================================================================
 
 // both the header and the implementation suppress all of these,
-// so it only makes sense to aggregrate them like so
+// so it only makes sense to aggregate them like so
 #define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH                                                      \
     DOCTEST_CLANG_SUPPRESS_WARNING_PUSH                                                            \
     DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")                                            \
     DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */                                          \
     DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */                                 \
     DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/                \
-    /* */                                                                                          \
+    /* common ones */                                                                              \
     DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */                             \
     DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */                             \
     DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */    \
     DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */              \
     DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */  \
     DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */                   \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */        \
     /* static analysis */                                                                          \
     DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */       \
     DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */                 \
@@ -236,7 +243,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly define
     DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \
     DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */                   \
     DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */     \
-    DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */
+    DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */  \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */
 
 #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
 
@@ -352,6 +360,12 @@ DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly define
 #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x)))
 #endif
 
+#ifdef DOCTEST_CONFIG_NO_CONTRADICTING_INLINE
+#define DOCTEST_INLINE_NOINLINE inline
+#else
+#define DOCTEST_INLINE_NOINLINE inline DOCTEST_NOINLINE
+#endif
+
 #ifndef DOCTEST_NORETURN
 #if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
 #define DOCTEST_NORETURN
@@ -378,6 +392,14 @@ DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly define
 #endif // DOCTEST_MSVC
 #endif // DOCTEST_CONSTEXPR
 
+#ifndef DOCTEST_NO_SANITIZE_INTEGER
+#if DOCTEST_CLANG >= DOCTEST_COMPILER(3, 7, 0)
+#define DOCTEST_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer")))
+#else
+#define DOCTEST_NO_SANITIZE_INTEGER
+#endif
+#endif // DOCTEST_NO_SANITIZE_INTEGER
+
 // =================================================================================================
 // == FEATURE DETECTION END ========================================================================
 // =================================================================================================
@@ -475,12 +497,13 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP
 // https://github.com/doctest/doctest/issues/356
 #if DOCTEST_CLANG
 #include <ciso646>
+#endif // clang
+
 #ifdef _LIBCPP_VERSION
 #ifndef DOCTEST_CONFIG_USE_STD_HEADERS
 #define DOCTEST_CONFIG_USE_STD_HEADERS
 #endif
 #endif // _LIBCPP_VERSION
-#endif // clang
 
 #ifdef DOCTEST_CONFIG_USE_STD_HEADERS
 #ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
@@ -970,7 +993,7 @@ namespace detail {
     struct deferred_false : types::false_type { };
 
 // MSVS 2015 :(
-#if defined(_MSC_VER) && _MSC_VER <= 1900
+#if !DOCTEST_CLANG && defined(_MSC_VER) && _MSC_VER <= 1900
     template <typename T, typename = void>
     struct has_global_insertion_operator : types::false_type { };
 
@@ -1000,8 +1023,13 @@ namespace detail {
     struct has_insertion_operator : types::false_type { };
 #endif
 
-template <typename T>
-struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };
+    template <typename T>
+    struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };
+
+    template <typename T>
+    struct should_stringify_as_underlying_type {
+        static DOCTEST_CONSTEXPR bool value = detail::types::is_enum<T>::value && !doctest::detail::has_insertion_operator<T>::value;
+    };
 
     DOCTEST_INTERFACE std::ostream* tlssPush();
     DOCTEST_INTERFACE String tlssPop();
@@ -1063,7 +1091,7 @@ struct StringMaker : public detail::StringMakerBase<
 
 template <typename T>
 String toString() {
-#if DOCTEST_MSVC >= 0 && DOCTEST_CLANG == 0 && DOCTEST_GCC == 0
+#if DOCTEST_CLANG == 0 && DOCTEST_GCC == 0 && DOCTEST_ICC == 0
     String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString<TYPE>(void)
     String::size_type beginPos = ret.find('<');
     return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast<String::size_type>(sizeof(">(void)")));
@@ -1074,7 +1102,7 @@ String toString() {
 #endif
 }
 
-template <typename T, typename detail::types::enable_if<!detail::types::is_enum<T>::value, bool>::type = true>
+template <typename T, typename detail::types::enable_if<!detail::should_stringify_as_underlying_type<T>::value, bool>::type = true>
 String toString(const DOCTEST_REF_WRAP(T) value) {
     return StringMaker<T>::convert(value);
 }
@@ -1110,7 +1138,7 @@ DOCTEST_INTERFACE String toString(long unsigned in);
 DOCTEST_INTERFACE String toString(long long in);
 DOCTEST_INTERFACE String toString(long long unsigned in);
 
-template <typename T, typename detail::types::enable_if<detail::types::is_enum<T>::value, bool>::type = true>
+template <typename T, typename detail::types::enable_if<detail::should_stringify_as_underlying_type<T>::value, bool>::type = true>
 String toString(const DOCTEST_REF_WRAP(T) value) {
     using UT = typename detail::types::underlying_type<T>::type;
     return (DOCTEST_STRINGIFY(static_cast<UT>(value)));
@@ -1162,8 +1190,18 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP
 
     template <typename T>
     struct filldata<T*> {
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4180)
         static void fill(std::ostream* stream, const T* in) {
-            filldata<const void*>::fill(stream, in);
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wmicrosoft-cast")
+            filldata<const void*>::fill(stream,
+#if DOCTEST_GCC == 0 || DOCTEST_GCC >= DOCTEST_COMPILER(4, 9, 0)
+                reinterpret_cast<const void*>(in)
+#else
+                *reinterpret_cast<const void* const*>(&in)
+#endif
+            );
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
         }
     };
 }
@@ -1275,9 +1313,9 @@ namespace detail {
     template<class T, unsigned N>   struct decay_array<T[N]> { using type = T*; };
     template<class T>               struct decay_array<T[]>  { using type = T*; };
 
-    template<class T>   struct not_char_pointer              { static DOCTEST_CONSTEXPR value = 1; };
-    template<>          struct not_char_pointer<char*>       { static DOCTEST_CONSTEXPR value = 0; };
-    template<>          struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR value = 0; };
+    template<class T>   struct not_char_pointer              { static DOCTEST_CONSTEXPR int value = 1; };
+    template<>          struct not_char_pointer<char*>       { static DOCTEST_CONSTEXPR int value = 0; };
+    template<>          struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR int value = 0; };
 
     template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {};
 #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
@@ -1326,7 +1364,11 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison")
 // If not it doesn't find the operator or if the operator at global scope is defined after
 // this template, the template won't be instantiated due to SFINAE. Once the template is not
 // instantiated it can look for global operator using normal conversions.
+#ifdef __NVCC__
+#define SFINAE_OP(ret,op) ret
+#else
 #define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{})
+#endif
 
 #define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro)                              \
     template <typename R>                                                                          \
@@ -2129,13 +2171,13 @@ int registerReporter(const char* name, int priority, bool isReporter) {
         {                                                                                          \
             void f();                                                                              \
         };                                                                                         \
-        static inline DOCTEST_NOINLINE void func() {                                               \
+        static DOCTEST_INLINE_NOINLINE void func() {                                               \
             der v;                                                                                 \
             v.f();                                                                                 \
         }                                                                                          \
         DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators)                                 \
     }                                                                                              \
-    inline DOCTEST_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers)
+    DOCTEST_INLINE_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers)
 
 #define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators)                                        \
     static void f();                                                                               \
@@ -3119,7 +3161,9 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 #include <utility>
 #include <fstream>
 #include <sstream>
+#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
 #include <iostream>
+#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
 #include <algorithm>
 #include <iomanip>
 #include <vector>
@@ -3156,9 +3200,11 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 // defines for a leaner windows.h
 #ifndef WIN32_LEAN_AND_MEAN
 #define WIN32_LEAN_AND_MEAN
+#define DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN
 #endif // WIN32_LEAN_AND_MEAN
 #ifndef NOMINMAX
 #define NOMINMAX
+#define DOCTEST_UNDEF_NOMINMAX
 #endif // NOMINMAX
 
 // not sure what AfxWin.h is for - here I do what Catch does
@@ -3239,8 +3285,14 @@ namespace {
 #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
         throw e;
 #else  // DOCTEST_CONFIG_NO_EXCEPTIONS
+#ifdef DOCTEST_CONFIG_HANDLE_EXCEPTION
+        DOCTEST_CONFIG_HANDLE_EXCEPTION(e);
+#else // DOCTEST_CONFIG_HANDLE_EXCEPTION
+#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
         std::cerr << "doctest will terminate because it needed to throw an exception.\n"
                   << "The message was: " << e.what() << '\n';
+#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
+#endif // DOCTEST_CONFIG_HANDLE_EXCEPTION
         std::terminate();
 #endif // DOCTEST_CONFIG_NO_EXCEPTIONS
     }
@@ -3315,7 +3367,7 @@ namespace detail {
 
 namespace timer_large_integer
 {
-    
+
 #if defined(DOCTEST_PLATFORM_WINDOWS)
     using type = ULONGLONG;
 #else // DOCTEST_PLATFORM_WINDOWS
@@ -3777,7 +3829,7 @@ namespace Color {
 
 // clang-format off
 const char* assertString(assertType::Enum at) {
-    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitely handled
+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitly handled
     #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type
     #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \
         DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \
@@ -4105,11 +4157,13 @@ namespace {
         return false;
     }
 
+    DOCTEST_NO_SANITIZE_INTEGER
     unsigned long long hash(unsigned long long a, unsigned long long b) {
         return (a << 5) + b;
     }
 
     // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
+    DOCTEST_NO_SANITIZE_INTEGER
     unsigned long long hash(const char* str) {
         unsigned long long hash = 5381;
         char c;
@@ -4949,7 +5003,7 @@ namespace detail {
             m_string = tlssPop();
             logged = true;
         }
-        
+
         DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
 
         const bool isWarn = m_severity & assertType::is_warn;
@@ -5018,7 +5072,11 @@ namespace {
             mutable XmlWriter* m_writer = nullptr;
         };
 
+#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
         XmlWriter( std::ostream& os = std::cout );
+#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
+        XmlWriter( std::ostream& os );
+#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
         ~XmlWriter();
 
         XmlWriter( XmlWriter const& ) = delete;
@@ -5500,7 +5558,7 @@ namespace {
             test_case_start_impl(in);
             xml.ensureTagClosed();
         }
-        
+
         void test_case_reenter(const TestCaseData&) override {}
 
         void test_case_end(const CurrentTestCaseStats& st) override {
@@ -5848,7 +5906,22 @@ namespace {
             testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str());
         }
 
-        void log_message(const MessageData&) override {}
+        void log_message(const MessageData& mb) override {
+            if(mb.m_severity & assertType::is_warn) // report only failures
+                return;
+
+            DOCTEST_LOCK_MUTEX(mutex)
+
+            std::ostringstream os;
+            os << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(")
+              << line(mb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl;
+
+            os << mb.m_string.c_str() << "\n";
+            log_contexts(os);
+
+            testCaseData.addFailure(mb.m_string.c_str(),
+                mb.m_severity & assertType::is_check ? "FAIL_CHECK" : "FAIL", os.str());
+        }
 
         void test_case_skipped(const TestCaseData&) override {}
 
@@ -6188,9 +6261,9 @@ namespace {
             separator_to_stream();
             s << std::dec;
 
-            auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)));
-            auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1)));
-            auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)));
+            auto totwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)));
+            auto passwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1)));
+            auto failwidth = int(std::ceil(log10(static_cast<double>(std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)));
             const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
             s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth)
               << p.numTestCasesPassingFilters << " | "
@@ -6222,7 +6295,7 @@ namespace {
             subcasesStack.clear();
             currentSubcaseLevel = 0;
         }
-        
+
         void test_case_reenter(const TestCaseData&) override {
             subcasesStack.clear();
         }
@@ -6739,8 +6812,12 @@ int Context::run() {
             fstr.open(p->out.c_str(), std::fstream::out);
             p->cout = &fstr;
         } else {
+#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
             // stdout by default
             p->cout = &std::cout;
+#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
+            return EXIT_FAILURE;
+#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM
         }
     }
 
@@ -6905,7 +6982,7 @@ int Context::run() {
             DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
 
             p->timer.start();
-            
+
             bool run_test = true;
 
             do {
@@ -6946,7 +7023,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP
                     run_test = false;
                     p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
                 }
-                
+
                 if(!p->nextSubcaseStack.empty() && run_test)
                     DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);
                 if(p->nextSubcaseStack.empty())
@@ -7017,3 +7094,13 @@ DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
 
 #endif // DOCTEST_LIBRARY_IMPLEMENTATION
 #endif // DOCTEST_CONFIG_IMPLEMENT
+
+#ifdef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN
+#undef WIN32_LEAN_AND_MEAN
+#undef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN
+#endif // DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN
+
+#ifdef DOCTEST_UNDEF_NOMINMAX
+#undef NOMINMAX
+#undef DOCTEST_UNDEF_NOMINMAX
+#endif // DOCTEST_UNDEF_NOMINMAX
index a7ebdd5..196a754 100644 (file)
@@ -548,6 +548,7 @@ public:
   ssize_t read(char *ptr, size_t size) override;
   ssize_t write(const char *ptr, size_t size) override;
   void get_remote_ip_and_port(std::string &ip, int &port) const override;
+  void get_local_ip_and_port(std::string &ip, int &port) const override;
   socket_t socket() const override;
 
 private:
@@ -577,6 +578,7 @@ public:
   ssize_t read(char *ptr, size_t size) override;
   ssize_t write(const char *ptr, size_t size) override;
   void get_remote_ip_and_port(std::string &ip, int &port) const override;
+  void get_local_ip_and_port(std::string &ip, int &port) const override;
   socket_t socket() const override;
 
 private:
@@ -693,14 +695,17 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
 
     auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
     if (sock != INVALID_SOCKET) {
-      sockaddr_un addr;
+      sockaddr_un addr{};
       addr.sun_family = AF_UNIX;
       std::copy(host.begin(), host.end(), addr.sun_path);
 
-      hints.ai_addr = reinterpret_cast<sockaddr*>(&addr);
+      hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
       hints.ai_addrlen = static_cast<socklen_t>(
           sizeof(addr) - sizeof(addr.sun_path) + addrlen);
 
+      fcntl(sock, F_SETFD, FD_CLOEXEC);
+      if (socket_options) { socket_options(sock); }
+
       if (!bind_or_connect(sock, hints)) {
         close_socket(sock);
         sock = INVALID_SOCKET;
@@ -748,7 +753,10 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
     if (sock == INVALID_SOCKET) { continue; }
 
 #ifndef _WIN32
-    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
+    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
+      close_socket(sock);
+      continue;
+    }
 #endif
 
     if (tcp_nodelay) {
@@ -821,7 +829,7 @@ bool bind_ip_address(socket_t sock, const std::string &host) {
   return ret;
 }
 
-#if !defined _WIN32 && !defined ANDROID
+#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
 #define USE_IF2IP
 #endif
 
@@ -942,9 +950,8 @@ socket_t create_client_socket(
   return sock;
 }
 
-bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
-                                   socklen_t addr_len, std::string &ip,
-                                   int &port) {
+bool get_ip_and_port(const struct sockaddr_storage &addr,
+                            socklen_t addr_len, std::string &ip, int &port) {
   if (addr.ss_family == AF_INET) {
     port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
   } else if (addr.ss_family == AF_INET6) {
@@ -965,21 +972,53 @@ bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
   return true;
 }
 
+void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
+  struct sockaddr_storage addr;
+  socklen_t addr_len = sizeof(addr);
+  if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
+                   &addr_len)) {
+    get_ip_and_port(addr, addr_len, ip, port);
+  }
+}
+
 void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
   struct sockaddr_storage addr;
   socklen_t addr_len = sizeof(addr);
 
   if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
                    &addr_len)) {
-    get_remote_ip_and_port(addr, addr_len, ip, port);
+#ifndef _WIN32
+    if (addr.ss_family == AF_UNIX) {
+#if defined(__linux__)
+      struct ucred ucred;
+      socklen_t len = sizeof(ucred);
+      if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
+        port = ucred.pid;
+      }
+#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__
+      pid_t pid;
+      socklen_t len = sizeof(pid);
+      if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
+        port = pid;
+      }
+#endif
+      return;
+    }
+#endif
+    get_ip_and_port(addr, addr_len, ip, port);
   }
 }
 
 constexpr unsigned int str2tag_core(const char *s, size_t l,
                                            unsigned int h) {
-  return (l == 0) ? h
-                  : str2tag_core(s + 1, l - 1,
-                                 (h * 33) ^ static_cast<unsigned char>(*s));
+  return (l == 0)
+             ? h
+             : str2tag_core(
+                   s + 1, l - 1,
+                   // Unsets the 6 high bits of h, therefore no overflow happens
+                   (((std::numeric_limits<unsigned int>::max)() >> 6) &
+                    h * 33) ^
+                       static_cast<unsigned char>(*s));
 }
 
 unsigned int str2tag(const std::string &s) {
@@ -1392,6 +1431,14 @@ const char *get_header_value(const Headers &headers,
   return def;
 }
 
+bool compare_case_ignore(const std::string &a, const std::string &b) {
+  if (a.size() != b.size()) { return false; }
+  for (size_t i = 0; i < b.size(); i++) {
+    if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
+  }
+  return true;
+}
+
 template <typename T>
 bool parse_header(const char *beg, const char *end, T fn) {
   // Skip trailing spaces and tabs.
@@ -1415,7 +1462,11 @@ bool parse_header(const char *beg, const char *end, T fn) {
   }
 
   if (p < end) {
-    fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
+    auto key = std::string(beg, key_end);
+    auto val = compare_case_ignore(key, "Location")
+                   ? std::string(p, end)
+                   : decode_url(std::string(p, end), false);
+    fn(std::move(key), std::move(val));
     return true;
   }
 
@@ -1513,7 +1564,8 @@ bool read_content_without_length(Stream &strm,
   return true;
 }
 
-bool read_content_chunked(Stream &strm,
+template <typename T>
+bool read_content_chunked(Stream &strm, T &x,
                                  ContentReceiverWithProgress out) {
   const auto bufsiz = 16;
   char buf[bufsiz];
@@ -1539,15 +1591,29 @@ bool read_content_chunked(Stream &strm,
 
     if (!line_reader.getline()) { return false; }
 
-    if (strcmp(line_reader.ptr(), "\r\n")) { break; }
+    if (strcmp(line_reader.ptr(), "\r\n")) { return false; }
 
     if (!line_reader.getline()) { return false; }
   }
 
-  if (chunk_len == 0) {
-    // Reader terminator after chunks
-    if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
-      return false;
+  assert(chunk_len == 0);
+
+  // Trailer
+  if (!line_reader.getline()) { return false; }
+
+  while (strcmp(line_reader.ptr(), "\r\n")) {
+    if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
+
+    // Exclude line terminator
+    constexpr auto line_terminator_len = 2;
+    auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
+
+    parse_header(line_reader.ptr(), end,
+                 [&](std::string &&key, std::string &&val) {
+                   x.headers.emplace(std::move(key), std::move(val));
+                 });
+
+    if (!line_reader.getline()) { return false; }
   }
 
   return true;
@@ -1617,7 +1683,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
         auto exceed_payload_max_length = false;
 
         if (is_chunked_transfer_encoding(x.headers)) {
-          ret = read_content_chunked(strm, out);
+          ret = read_content_chunked(strm, x, out);
         } else if (!has_header(x.headers, "Content-Length")) {
           ret = read_content_without_length(strm, out);
         } else {
@@ -1670,7 +1736,7 @@ bool write_content(Stream &strm, const ContentProvider &content_provider,
 
   data_sink.write = [&](const char *d, size_t l) -> bool {
     if (ok) {
-      if (write_data(strm, d, l)) {
+      if (strm.is_writable() && write_data(strm, d, l)) {
         offset += l;
       } else {
         ok = false;
@@ -1679,14 +1745,14 @@ bool write_content(Stream &strm, const ContentProvider &content_provider,
     return ok;
   };
 
-  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
-
   while (offset < end_offset && !is_shutting_down()) {
-    if (!content_provider(offset, end_offset - offset, data_sink)) {
+    if (!strm.is_writable()) {
+      error = Error::Write;
+      return false;
+    } else if (!content_provider(offset, end_offset - offset, data_sink)) {
       error = Error::Canceled;
       return false;
-    }
-    if (!ok) {
+    } else if (!ok) {
       error = Error::Write;
       return false;
     }
@@ -1718,18 +1784,21 @@ write_content_without_length(Stream &strm,
   data_sink.write = [&](const char *d, size_t l) -> bool {
     if (ok) {
       offset += l;
-      if (!write_data(strm, d, l)) { ok = false; }
+      if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; }
     }
     return ok;
   };
 
   data_sink.done = [&](void) { data_available = false; };
 
-  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
-
   while (data_available && !is_shutting_down()) {
-    if (!content_provider(offset, 0, data_sink)) { return false; }
-    if (!ok) { return false; }
+    if (!strm.is_writable()) {
+      return false;
+    } else if (!content_provider(offset, 0, data_sink)) {
+      return false;
+    } else if (!ok) {
+      return false;
+    }
   }
   return true;
 }
@@ -1758,7 +1827,10 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
           // Emit chunked response header and footer for each chunk
           auto chunk =
               from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
-          if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
+          if (!strm.is_writable() ||
+              !write_data(strm, chunk.data(), chunk.size())) {
+            ok = false;
+          }
         }
       } else {
         ok = false;
@@ -1767,7 +1839,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
     return ok;
   };
 
-  data_sink.done = [&](void) {
+  auto done_with_trailer = [&](const Headers *trailer) {
     if (!ok) { return; }
 
     data_available = false;
@@ -1785,26 +1857,46 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
     if (!payload.empty()) {
       // Emit chunked response header and footer for each chunk
       auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
-      if (!write_data(strm, chunk.data(), chunk.size())) {
+      if (!strm.is_writable() ||
+          !write_data(strm, chunk.data(), chunk.size())) {
         ok = false;
         return;
       }
     }
 
-    static const std::string done_marker("0\r\n\r\n");
+    static const std::string done_marker("0\r\n");
     if (!write_data(strm, done_marker.data(), done_marker.size())) {
       ok = false;
     }
+
+    // Trailer
+    if (trailer) {
+      for (const auto &kv : *trailer) {
+        std::string field_line = kv.first + ": " + kv.second + "\r\n";
+        if (!write_data(strm, field_line.data(), field_line.size())) {
+          ok = false;
+        }
+      }
+    }
+
+    static const std::string crlf("\r\n");
+    if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; }
   };
 
-  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+  data_sink.done = [&](void) { done_with_trailer(nullptr); };
+
+  data_sink.done_with_trailer = [&](const Headers &trailer) {
+    done_with_trailer(&trailer);
+  };
 
   while (data_available && !is_shutting_down()) {
-    if (!content_provider(offset, 0, data_sink)) {
+    if (!strm.is_writable()) {
+      error = Error::Write;
+      return false;
+    } else if (!content_provider(offset, 0, data_sink)) {
       error = Error::Canceled;
       return false;
-    }
-    if (!ok) {
+    } else if (!ok) {
       error = Error::Write;
       return false;
     }
@@ -1885,9 +1977,12 @@ void parse_query_text(const std::string &s, Params &params) {
 
 bool parse_multipart_boundary(const std::string &content_type,
                                      std::string &boundary) {
-  auto pos = content_type.find("boundary=");
+  auto boundary_keyword = "boundary=";
+  auto pos = content_type.find(boundary_keyword);
   if (pos == std::string::npos) { return false; }
-  boundary = content_type.substr(pos + 9);
+  auto end = content_type.find(';', pos);
+  auto beg = pos + strlen(boundary_keyword);
+  boundary = content_type.substr(beg, end - beg);
   if (boundary.length() >= 2 && boundary.front() == '"' &&
       boundary.back() == '"') {
     boundary = boundary.substr(1, boundary.size() - 2);
@@ -1998,6 +2093,9 @@ public:
             if (std::regex_match(header, m, re_content_disposition)) {
               file_.name = m[1];
               file_.filename = m[2];
+            } else {
+              is_valid_ = false;
+              return false;
             }
           }
           buf_erase(pos + crlf_.size());
@@ -2179,6 +2277,63 @@ std::string make_multipart_data_boundary() {
   return result;
 }
 
+bool is_multipart_boundary_chars_valid(const std::string &boundary) {
+  auto valid = true;
+  for (size_t i = 0; i < boundary.size(); i++) {
+    auto c = boundary[i];
+    if (!std::isalnum(c) && c != '-' && c != '_') {
+      valid = false;
+      break;
+    }
+  }
+  return valid;
+}
+
+template <typename T>
+std::string
+serialize_multipart_formdata_item_begin(const T &item,
+                                        const std::string &boundary) {
+  std::string body = "--" + boundary + "\r\n";
+  body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
+  if (!item.filename.empty()) {
+    body += "; filename=\"" + item.filename + "\"";
+  }
+  body += "\r\n";
+  if (!item.content_type.empty()) {
+    body += "Content-Type: " + item.content_type + "\r\n";
+  }
+  body += "\r\n";
+
+  return body;
+}
+
+std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
+
+std::string
+serialize_multipart_formdata_finish(const std::string &boundary) {
+  return "--" + boundary + "--\r\n";
+}
+
+std::string
+serialize_multipart_formdata_get_content_type(const std::string &boundary) {
+  return "multipart/form-data; boundary=" + boundary;
+}
+
+std::string
+serialize_multipart_formdata(const MultipartFormDataItems &items,
+                             const std::string &boundary, bool finish = true) {
+  std::string body;
+
+  for (const auto &item : items) {
+    body += serialize_multipart_formdata_item_begin(item, boundary);
+    body += item.content + serialize_multipart_formdata_item_end();
+  }
+
+  if (finish) body += serialize_multipart_formdata_finish(boundary);
+
+  return body;
+}
+
 std::pair<size_t, size_t>
 get_range_offset_and_length(const Request &req, size_t content_length,
                             size_t index) {
@@ -2360,15 +2515,15 @@ std::string SHA_512(const std::string &s) {
 }
 #endif
 
-#ifdef _WIN32
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+#ifdef _WIN32
 // NOTE: This code came up with the following stackoverflow post:
 // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
 bool load_system_certs_on_windows(X509_STORE *store) {
   auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
-
   if (!hStore) { return false; }
 
+  auto result = false;
   PCCERT_CONTEXT pContext = NULL;
   while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
          nullptr) {
@@ -2379,16 +2534,109 @@ bool load_system_certs_on_windows(X509_STORE *store) {
     if (x509) {
       X509_STORE_add_cert(store, x509);
       X509_free(x509);
+      result = true;
     }
   }
 
   CertFreeCertificateContext(pContext);
   CertCloseStore(hStore, 0);
 
+  return result;
+}
+#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
+#if TARGET_OS_OSX
+template <typename T>
+using CFObjectPtr =
+    std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>;
+
+void cf_object_ptr_deleter(CFTypeRef obj) {
+  if (obj) { CFRelease(obj); }
+}
+
+bool retrieve_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
+  CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
+  CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll,
+                        kCFBooleanTrue};
+
+  CFObjectPtr<CFDictionaryRef> query(
+      CFDictionaryCreate(nullptr, reinterpret_cast<const void **>(keys), values,
+                         sizeof(keys) / sizeof(keys[0]),
+                         &kCFTypeDictionaryKeyCallBacks,
+                         &kCFTypeDictionaryValueCallBacks),
+      cf_object_ptr_deleter);
+
+  if (!query) { return false; }
+
+  CFTypeRef security_items = nullptr;
+  if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess ||
+      CFArrayGetTypeID() != CFGetTypeID(security_items)) {
+    return false;
+  }
+
+  certs.reset(reinterpret_cast<CFArrayRef>(security_items));
   return true;
 }
-#endif
 
+bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
+  CFArrayRef root_security_items = nullptr;
+  if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) {
+    return false;
+  }
+
+  certs.reset(root_security_items);
+  return true;
+}
+
+bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
+  auto result = false;
+  for (int i = 0; i < CFArrayGetCount(certs); ++i) {
+    const auto cert = reinterpret_cast<const __SecCertificate *>(
+        CFArrayGetValueAtIndex(certs, i));
+
+    if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; }
+
+    CFDataRef cert_data = nullptr;
+    if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) !=
+        errSecSuccess) {
+      continue;
+    }
+
+    CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
+
+    auto encoded_cert = static_cast<const unsigned char *>(
+        CFDataGetBytePtr(cert_data_ptr.get()));
+
+    auto x509 =
+        d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get()));
+
+    if (x509) {
+      X509_STORE_add_cert(store, x509);
+      X509_free(x509);
+      result = true;
+    }
+  }
+
+  return result;
+}
+
+bool load_system_certs_on_macos(X509_STORE *store) {
+  auto result = false;
+  CFObjectPtr<CFArrayRef> certs(nullptr, cf_object_ptr_deleter);
+  if (retrieve_certs_from_keychain(certs) && certs) {
+    result = add_certs_to_x509_store(certs.get(), store);
+  }
+
+  if (retrieve_root_certs_from_keychain(certs) && certs) {
+    result = add_certs_to_x509_store(certs.get(), store) || result;
+  }
+
+  return result;
+}
+#endif // TARGET_OS_OSX
+#endif // _WIN32
+#endif // CPPHTTPLIB_OPENSSL_SUPPORT
+
+#ifdef _WIN32
 class WSInit {
 public:
   WSInit() {
@@ -2560,8 +2808,8 @@ void hosted_at(const std::string &hostname,
         *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
     std::string ip;
     int dummy = -1;
-    if (detail::get_remote_ip_and_port(addr, sizeof(struct sockaddr_storage),
-                                       ip, dummy)) {
+    if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
+                                dummy)) {
       addrs.push_back(ip);
     }
   }
@@ -2663,6 +2911,16 @@ MultipartFormData Request::get_file_value(const std::string &key) const {
   return MultipartFormData();
 }
 
+std::vector<MultipartFormData>
+Request::get_file_values(const std::string &key) const {
+  std::vector<MultipartFormData> values;
+  auto rng = files.equal_range(key);
+  for (auto it = rng.first; it != rng.second; it++) {
+    values.push_back(it->second);
+  }
+  return values;
+}
+
 // Response implementation
 bool Response::has_header(const std::string &key) const {
   return headers.find(key) != headers.end();
@@ -2713,10 +2971,9 @@ void Response::set_content(const std::string &s,
 void Response::set_content_provider(
     size_t in_length, const std::string &content_type, ContentProvider provider,
     ContentProviderResourceReleaser resource_releaser) {
-  assert(in_length > 0);
   set_header("Content-Type", content_type);
   content_length_ = in_length;
-  content_provider_ = std::move(provider);
+  if (in_length > 0) { content_provider_ = std::move(provider); }
   content_provider_resource_releaser_ = resource_releaser;
   is_chunked_content_provider_ = false;
 }
@@ -2785,7 +3042,8 @@ bool SocketStream::is_readable() const {
 }
 
 bool SocketStream::is_writable() const {
-  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
+  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
+         is_socket_alive(sock_);
 }
 
 ssize_t SocketStream::read(char *ptr, size_t size) {
@@ -2850,6 +3108,11 @@ void SocketStream::get_remote_ip_and_port(std::string &ip,
   return detail::get_remote_ip_and_port(sock_, ip, port);
 }
 
+void SocketStream::get_local_ip_and_port(std::string &ip,
+                                                int &port) const {
+  return detail::get_local_ip_and_port(sock_, ip, port);
+}
+
 socket_t SocketStream::socket() const { return sock_; }
 
 // Buffer stream implementation
@@ -2875,6 +3138,9 @@ ssize_t BufferStream::write(const char *ptr, size_t size) {
 void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
                                                  int & /*port*/) const {}
 
+void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
+                                                int & /*port*/) const {}
+
 socket_t BufferStream::socket() const { return 0; }
 
 const std::string &BufferStream::get_buffer() const { return buffer; }
@@ -2884,8 +3150,7 @@ const std::string &BufferStream::get_buffer() const { return buffer; }
 // HTTP server implementation
 Server::Server()
     : new_task_queue(
-          [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
-      svr_sock_(INVALID_SOCKET), is_running_(false) {
+          [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) {
 #ifndef _WIN32
   signal(SIGPIPE, SIG_IGN);
 #endif
@@ -3032,7 +3297,6 @@ Server &Server::set_logger(Logger logger) {
 Server &
 Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
   expect_100_continue_handler_ = std::move(handler);
-
   return *this;
 }
 
@@ -3098,15 +3362,25 @@ int Server::bind_to_any_port(const std::string &host, int socket_flags) {
   return bind_internal(host, 0, socket_flags);
 }
 
-bool Server::listen_after_bind() { return listen_internal(); }
+bool Server::listen_after_bind() {
+  auto se = detail::scope_exit([&]() { done_ = true; });
+  return listen_internal();
+}
 
 bool Server::listen(const std::string &host, int port,
                            int socket_flags) {
+  auto se = detail::scope_exit([&]() { done_ = true; });
   return bind_to_port(host, port, socket_flags) && listen_internal();
 }
 
 bool Server::is_running() const { return is_running_; }
 
+void Server::wait_until_ready() const {
+  while (!is_running() && !done_) {
+    std::this_thread::sleep_for(std::chrono::milliseconds{1});
+  }
+}
+
 void Server::stop() {
   if (is_running_) {
     assert(svr_sock_ != INVALID_SOCKET);
@@ -3322,6 +3596,7 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
 
 bool Server::read_content(Stream &strm, Request &req, Response &res) {
   MultipartFormDataMap::iterator cur;
+  auto file_count = 0;
   if (read_content_core(
           strm, req, res,
           // Regular
@@ -3332,6 +3607,9 @@ bool Server::read_content(Stream &strm, Request &req, Response &res) {
           },
           // Multipart
           [&](const MultipartFormData &file) {
+            if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
+              return false;
+            }
             cur = req.files.emplace(file.name, file);
             return true;
           },
@@ -3365,7 +3643,7 @@ bool Server::read_content_with_content_receiver(
 
 bool Server::read_content_core(Stream &strm, Request &req, Response &res,
                                       ContentReceiver receiver,
-                                      MultipartContentHeader mulitpart_header,
+                                      MultipartContentHeader multipart_header,
                                       ContentReceiver multipart_receiver) {
   detail::MultipartFormDataParser multipart_form_data_parser;
   ContentReceiverWithProgress out;
@@ -3385,14 +3663,14 @@ bool Server::read_content_core(Stream &strm, Request &req, Response &res,
       while (pos < n) {
         auto read_size = (std::min)<size_t>(1, n - pos);
         auto ret = multipart_form_data_parser.parse(
-            buf + pos, read_size, multipart_receiver, mulitpart_header);
+            buf + pos, read_size, multipart_receiver, multipart_header);
         if (!ret) { return false; }
         pos += read_size;
       }
       return true;
       */
       return multipart_form_data_parser.parse(buf, n, multipart_receiver,
-                                              mulitpart_header);
+                                              multipart_header);
     };
   } else {
     out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
@@ -3493,6 +3771,7 @@ int Server::bind_internal(const std::string &host, int port,
 bool Server::listen_internal() {
   auto ret = true;
   is_running_ = true;
+  auto se = detail::scope_exit([&]() { is_running_ = false; });
 
   {
     std::unique_ptr<TaskQueue> task_queue(new_task_queue());
@@ -3518,6 +3797,8 @@ bool Server::listen_internal() {
           // Try to accept new connections after a short sleep.
           std::this_thread::sleep_for(std::chrono::milliseconds(1));
           continue;
+        } else if (errno == EINTR || errno == EAGAIN) {
+          continue;
         }
         if (svr_sock_ != INVALID_SOCKET) {
           detail::close_socket(svr_sock_);
@@ -3556,17 +3837,12 @@ bool Server::listen_internal() {
 #endif
       }
 
-#if __cplusplus > 201703L
-      task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
-#else
-      task_queue->enqueue([=]() { process_and_close_socket(sock); });
-#endif
+      task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
     }
 
     task_queue->shutdown();
   }
 
-  is_running_ = false;
   return ret;
 }
 
@@ -3673,8 +3949,8 @@ void Server::apply_ranges(const Request &req, Response &res,
       res.headers.erase(it);
     }
 
-    res.headers.emplace("Content-Type",
-                        "multipart/byteranges; boundary=" + boundary);
+    res.set_header("Content-Type",
+                   "multipart/byteranges; boundary=" + boundary);
   }
 
   auto type = detail::encoding_type(req, res);
@@ -3850,6 +4126,10 @@ Server::process_request(Stream &strm, bool close_connection,
   req.set_header("REMOTE_ADDR", req.remote_addr);
   req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
 
+  strm.get_local_ip_and_port(req.local_addr, req.local_port);
+  req.set_header("LOCAL_ADDR", req.local_addr);
+  req.set_header("LOCAL_PORT", std::to_string(req.local_port));
+
   if (req.has_header("Range")) {
     const auto &range_header_value = req.get_header_value("Range");
     if (!detail::parse_range_header(range_header_value, req.ranges)) {
@@ -3889,7 +4169,16 @@ Server::process_request(Stream &strm, bool close_connection,
       routed = true;
     } else {
       res.status = 500;
-      res.set_header("EXCEPTION_WHAT", e.what());
+      std::string val;
+      auto s = e.what();
+      for (size_t i = 0; s[i]; i++) {
+        switch (s[i]) {
+        case '\r': val += "\\r"; break;
+        case '\n': val += "\\n"; break;
+        default: val += s[i]; break;
+        }
+      }
+      res.set_header("EXCEPTION_WHAT", val);
     }
   } catch (...) {
     if (exception_handler_) {
@@ -4094,7 +4383,15 @@ bool ClientImpl::read_response_line(Stream &strm, const Request &req,
 
 bool ClientImpl::send(Request &req, Response &res, Error &error) {
   std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
+  auto ret = send_(req, res, error);
+  if (error == Error::SSLPeerCouldBeClosed_) {
+    assert(!ret);
+    ret = send_(req, res, error);
+  }
+  return ret;
+}
 
+bool ClientImpl::send_(Request &req, Response &res, Error &error) {
   {
     std::lock_guard<std::mutex> guard(socket_mutex_);
 
@@ -4125,7 +4422,7 @@ bool ClientImpl::send(Request &req, Response &res, Error &error) {
       if (is_ssl()) {
         auto &scli = static_cast<SSLClient &>(*this);
         if (!proxy_host_.empty() && proxy_port_ != -1) {
-          bool success = false;
+          auto success = false;
           if (!scli.connect_with_proxy(socket_, res, success, error)) {
             return success;
           }
@@ -4152,13 +4449,11 @@ bool ClientImpl::send(Request &req, Response &res, Error &error) {
     }
   }
 
+  auto ret = false;
   auto close_connection = !keep_alive_;
-  auto ret = process_socket(socket_, [&](Stream &strm) {
-    return handle_request(strm, req, res, close_connection, error);
-  });
 
-  // Briefly lock mutex in order to mark that a request is no longer ongoing
-  {
+  auto se = detail::scope_exit([&]() {
+    // Briefly lock mutex in order to mark that a request is no longer ongoing
     std::lock_guard<std::mutex> guard(socket_mutex_);
     socket_requests_in_flight_ -= 1;
     if (socket_requests_in_flight_ <= 0) {
@@ -4172,7 +4467,11 @@ bool ClientImpl::send(Request &req, Response &res, Error &error) {
       shutdown_socket(socket_);
       close_socket(socket_);
     }
-  }
+  });
+
+  ret = process_socket(socket_, [&](Stream &strm) {
+    return handle_request(strm, req, res, close_connection, error);
+  });
 
   if (!ret) {
     if (error == Error::Success) { error = Error::Unknown; }
@@ -4260,11 +4559,11 @@ bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
     return false;
   }
 
-  auto location = detail::decode_url(res.get_header_value("location"), true);
+  auto location = res.get_header_value("location");
   if (location.empty()) { return false; }
 
   const static std::regex re(
-      R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
+      R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
 
   std::smatch m;
   if (!std::regex_match(location, m, re)) { return false; }
@@ -4276,6 +4575,7 @@ bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
   if (next_host.empty()) { next_host = m[3].str(); }
   auto port_str = m[4].str();
   auto next_path = m[5].str();
+  auto next_query = m[6].str();
 
   auto next_port = port_;
   if (!port_str.empty()) {
@@ -4288,22 +4588,24 @@ bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
   if (next_host.empty()) { next_host = host_; }
   if (next_path.empty()) { next_path = "/"; }
 
+  auto path = detail::decode_url(next_path, true) + next_query;
+
   if (next_scheme == scheme && next_host == host_ && next_port == port_) {
-    return detail::redirect(*this, req, res, next_path, location, error);
+    return detail::redirect(*this, req, res, path, location, error);
   } else {
     if (next_scheme == "https") {
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
       SSLClient cli(next_host.c_str(), next_port);
       cli.copy_settings(*this);
       if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); }
-      return detail::redirect(cli, req, res, next_path, location, error);
+      return detail::redirect(cli, req, res, path, location, error);
 #else
       return false;
 #endif
     } else {
       ClientImpl cli(next_host.c_str(), next_port);
       cli.copy_settings(*this);
-      return detail::redirect(cli, req, res, next_path, location, error);
+      return detail::redirect(cli, req, res, path, location, error);
     }
   }
 }
@@ -4314,7 +4616,7 @@ bool ClientImpl::write_content_with_provider(Stream &strm,
   auto is_shutting_down = []() { return false; };
 
   if (req.is_chunked_content_provider_) {
-    // TODO: Brotli suport
+    // TODO: Brotli support
     std::unique_ptr<detail::compressor> compressor;
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
     if (compress_) {
@@ -4331,39 +4633,39 @@ bool ClientImpl::write_content_with_provider(Stream &strm,
     return detail::write_content(strm, req.content_provider_, 0,
                                  req.content_length_, is_shutting_down, error);
   }
-} // namespace httplib
+}
 
 bool ClientImpl::write_request(Stream &strm, Request &req,
                                       bool close_connection, Error &error) {
   // Prepare additional headers
   if (close_connection) {
     if (!req.has_header("Connection")) {
-      req.headers.emplace("Connection", "close");
+      req.set_header("Connection", "close");
     }
   }
 
   if (!req.has_header("Host")) {
     if (is_ssl()) {
       if (port_ == 443) {
-        req.headers.emplace("Host", host_);
+        req.set_header("Host", host_);
       } else {
-        req.headers.emplace("Host", host_and_port_);
+        req.set_header("Host", host_and_port_);
       }
     } else {
       if (port_ == 80) {
-        req.headers.emplace("Host", host_);
+        req.set_header("Host", host_);
       } else {
-        req.headers.emplace("Host", host_and_port_);
+        req.set_header("Host", host_and_port_);
       }
     }
   }
 
-  if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
+  if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
 
 #ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
   if (!req.has_header("User-Agent")) {
     auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
-    req.headers.emplace("User-Agent", agent);
+    req.set_header("User-Agent", agent);
   }
 #endif
 
@@ -4372,23 +4674,23 @@ bool ClientImpl::write_request(Stream &strm, Request &req,
       if (!req.is_chunked_content_provider_) {
         if (!req.has_header("Content-Length")) {
           auto length = std::to_string(req.content_length_);
-          req.headers.emplace("Content-Length", length);
+          req.set_header("Content-Length", length);
         }
       }
     } else {
       if (req.method == "POST" || req.method == "PUT" ||
           req.method == "PATCH") {
-        req.headers.emplace("Content-Length", "0");
+        req.set_header("Content-Length", "0");
       }
     }
   } else {
     if (!req.has_header("Content-Type")) {
-      req.headers.emplace("Content-Type", "text/plain");
+      req.set_header("Content-Type", "text/plain");
     }
 
     if (!req.has_header("Content-Length")) {
       auto length = std::to_string(req.body.size());
-      req.headers.emplace("Content-Length", length);
+      req.set_header("Content-Length", length);
     }
   }
 
@@ -4456,12 +4758,10 @@ std::unique_ptr<Response> ClientImpl::send_with_content_provider(
     ContentProvider content_provider,
     ContentProviderWithoutLength content_provider_without_length,
     const std::string &content_type, Error &error) {
-  if (!content_type.empty()) {
-    req.headers.emplace("Content-Type", content_type);
-  }
+  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-  if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); }
+  if (compress_) { req.set_header("Content-Encoding", "gzip"); }
 #endif
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
@@ -4494,8 +4794,6 @@ std::unique_ptr<Response> ClientImpl::send_with_content_provider(
         return ok;
       };
 
-      data_sink.is_writable = [&](void) { return ok && true; };
-
       while (ok && offset < content_length) {
         if (!content_provider(offset, content_length - offset, data_sink)) {
           error = Error::Canceled;
@@ -4524,10 +4822,9 @@ std::unique_ptr<Response> ClientImpl::send_with_content_provider(
       req.content_provider_ = detail::ContentProviderAdapter(
           std::move(content_provider_without_length));
       req.is_chunked_content_provider_ = true;
-      req.headers.emplace("Transfer-Encoding", "chunked");
+      req.set_header("Transfer-Encoding", "chunked");
     } else {
       req.body.assign(body, content_length);
-      ;
     }
   }
 
@@ -4566,6 +4863,20 @@ bool ClientImpl::process_request(Stream &strm, Request &req,
   // Send request
   if (!write_request(strm, req, close_connection, error)) { return false; }
 
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+  if (is_ssl()) {
+    auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
+    if (!is_proxy_enabled) {
+      char buf[1];
+      if (SSL_peek(socket_.ssl, buf, 1) == 0 &&
+          SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) {
+        error = Error::SSLPeerCouldBeClosed_;
+        return false;
+      }
+    }
+  }
+#endif
+
   // Receive response and headers
   if (!read_response_line(strm, req, res) ||
       !detail::read_headers(strm, res.headers)) {
@@ -4643,6 +4954,48 @@ bool ClientImpl::process_request(Stream &strm, Request &req,
   return true;
 }
 
+ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
+    const std::string &boundary, const MultipartFormDataItems &items,
+    const MultipartFormDataProviderItems &provider_items) {
+  size_t cur_item = 0, cur_start = 0;
+  // cur_item and cur_start are copied to within the std::function and maintain
+  // state between successive calls
+  return [&, cur_item, cur_start](size_t offset,
+                                  DataSink &sink) mutable -> bool {
+    if (!offset && items.size()) {
+      sink.os << detail::serialize_multipart_formdata(items, boundary, false);
+      return true;
+    } else if (cur_item < provider_items.size()) {
+      if (!cur_start) {
+        const auto &begin = detail::serialize_multipart_formdata_item_begin(
+            provider_items[cur_item], boundary);
+        offset += begin.size();
+        cur_start = offset;
+        sink.os << begin;
+      }
+
+      DataSink cur_sink;
+      bool has_data = true;
+      cur_sink.write = sink.write;
+      cur_sink.done = [&]() { has_data = false; };
+
+      if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
+        return false;
+
+      if (!has_data) {
+        sink.os << detail::serialize_multipart_formdata_item_end();
+        cur_item++;
+        cur_start = 0;
+      }
+      return true;
+    } else {
+      sink.os << detail::serialize_multipart_formdata_finish(boundary);
+      sink.done();
+      return true;
+    }
+  };
+}
+
 bool
 ClientImpl::process_socket(const Socket &socket,
                            std::function<bool(Stream &strm)> callback) {
@@ -4788,6 +5141,11 @@ Result ClientImpl::Post(const std::string &path) {
   return Post(path, std::string(), std::string());
 }
 
+Result ClientImpl::Post(const std::string &path,
+                               const Headers &headers) {
+  return Post(path, headers, nullptr, 0, std::string());
+}
+
 Result ClientImpl::Post(const std::string &path, const char *body,
                                size_t content_length,
                                const std::string &content_type) {
@@ -4860,40 +5218,39 @@ Result ClientImpl::Post(const std::string &path,
 
 Result ClientImpl::Post(const std::string &path, const Headers &headers,
                                const MultipartFormDataItems &items) {
-  return Post(path, headers, items, detail::make_multipart_data_boundary());
+  const auto &boundary = detail::make_multipart_data_boundary();
+  const auto &content_type =
+      detail::serialize_multipart_formdata_get_content_type(boundary);
+  const auto &body = detail::serialize_multipart_formdata(items, boundary);
+  return Post(path, headers, body, content_type.c_str());
 }
+
 Result ClientImpl::Post(const std::string &path, const Headers &headers,
                                const MultipartFormDataItems &items,
                                const std::string &boundary) {
-  for (size_t i = 0; i < boundary.size(); i++) {
-    char c = boundary[i];
-    if (!std::isalnum(c) && c != '-' && c != '_') {
-      return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
-    }
+  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
+    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
   }
 
-  std::string body;
-
-  for (const auto &item : items) {
-    body += "--" + boundary + "\r\n";
-    body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
-    if (!item.filename.empty()) {
-      body += "; filename=\"" + item.filename + "\"";
-    }
-    body += "\r\n";
-    if (!item.content_type.empty()) {
-      body += "Content-Type: " + item.content_type + "\r\n";
-    }
-    body += "\r\n";
-    body += item.content + "\r\n";
-  }
-
-  body += "--" + boundary + "--\r\n";
-
-  std::string content_type = "multipart/form-data; boundary=" + boundary;
+  const auto &content_type =
+      detail::serialize_multipart_formdata_get_content_type(boundary);
+  const auto &body = detail::serialize_multipart_formdata(items, boundary);
   return Post(path, headers, body, content_type.c_str());
 }
 
+Result
+ClientImpl::Post(const std::string &path, const Headers &headers,
+                 const MultipartFormDataItems &items,
+                 const MultipartFormDataProviderItems &provider_items) {
+  const auto &boundary = detail::make_multipart_data_boundary();
+  const auto &content_type =
+      detail::serialize_multipart_formdata_get_content_type(boundary);
+  return send_with_content_provider(
+      "POST", path, headers, nullptr, 0, nullptr,
+      get_multipart_content_provider(boundary, items, provider_items),
+      content_type);
+}
+
 Result ClientImpl::Put(const std::string &path) {
   return Put(path, std::string(), std::string());
 }
@@ -4963,6 +5320,45 @@ Result ClientImpl::Put(const std::string &path, const Headers &headers,
   return Put(path, headers, query, "application/x-www-form-urlencoded");
 }
 
+Result ClientImpl::Put(const std::string &path,
+                              const MultipartFormDataItems &items) {
+  return Put(path, Headers(), items);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+                              const MultipartFormDataItems &items) {
+  const auto &boundary = detail::make_multipart_data_boundary();
+  const auto &content_type =
+      detail::serialize_multipart_formdata_get_content_type(boundary);
+  const auto &body = detail::serialize_multipart_formdata(items, boundary);
+  return Put(path, headers, body, content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+                              const MultipartFormDataItems &items,
+                              const std::string &boundary) {
+  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
+    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
+  }
+
+  const auto &content_type =
+      detail::serialize_multipart_formdata_get_content_type(boundary);
+  const auto &body = detail::serialize_multipart_formdata(items, boundary);
+  return Put(path, headers, body, content_type);
+}
+
+Result
+ClientImpl::Put(const std::string &path, const Headers &headers,
+                const MultipartFormDataItems &items,
+                const MultipartFormDataProviderItems &provider_items) {
+  const auto &boundary = detail::make_multipart_data_boundary();
+  const auto &content_type =
+      detail::serialize_multipart_formdata_get_content_type(boundary);
+  return send_with_content_provider(
+      "PUT", path, headers, nullptr, 0, nullptr,
+      get_multipart_content_provider(boundary, items, provider_items),
+      content_type);
+}
 Result ClientImpl::Patch(const std::string &path) {
   return Patch(path, std::string(), std::string());
 }
@@ -5048,9 +5444,7 @@ Result ClientImpl::Delete(const std::string &path,
   req.headers = headers;
   req.path = path;
 
-  if (!content_type.empty()) {
-    req.headers.emplace("Content-Type", content_type);
-  }
+  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
   req.body.assign(body, content_length);
 
   return send_(std::move(req));
@@ -5088,9 +5482,7 @@ size_t ClientImpl::is_socket_open() const {
   return socket_.is_open();
 }
 
-socket_t ClientImpl::socket() const {
-  return socket_.sock;
-}
+socket_t ClientImpl::socket() const { return socket_.sock; }
 
 void ClientImpl::stop() {
   std::lock_guard<std::mutex> guard(socket_mutex_);
@@ -5109,7 +5501,7 @@ void ClientImpl::stop() {
     return;
   }
 
-  // Otherwise, sitll holding the mutex, we can shut everything down ourselves
+  // Otherwise, still holding the mutex, we can shut everything down ourselves
   shutdown_ssl(socket_, true);
   shutdown_socket(socket_);
   close_socket(socket_);
@@ -5202,9 +5594,7 @@ void ClientImpl::set_proxy_digest_auth(const std::string &username,
   proxy_digest_auth_username_ = username;
   proxy_digest_auth_password_ = password;
 }
-#endif
 
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
                                          const std::string &ca_cert_dir_path) {
   ca_cert_file_path_ = ca_cert_file_path;
@@ -5216,9 +5606,34 @@ void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
     ca_cert_store_ = ca_cert_store;
   }
 }
-#endif
 
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
+                                                    std::size_t size) {
+  auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
+  if (!mem) return nullptr;
+
+  auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
+  if (!inf) {
+    BIO_free_all(mem);
+    return nullptr;
+  }
+
+  auto cts = X509_STORE_new();
+  if (cts) {
+    for (auto first = 0, last = sk_X509_INFO_num(inf); first < last; ++first) {
+      auto itmp = sk_X509_INFO_value(inf, first);
+      if (!itmp) { continue; }
+
+      if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
+      if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
+    }
+  }
+
+  sk_X509_INFO_pop_free(inf, X509_INFO_free);
+  BIO_free_all(mem);
+  return cts;
+}
+
 void ClientImpl::enable_server_certificate_verification(bool enabled) {
   server_certificate_verification_ = enabled;
 }
@@ -5324,55 +5739,12 @@ process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
   return callback(strm);
 }
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
-static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
-
-class SSLThreadLocks {
-public:
-  SSLThreadLocks() {
-    openSSL_locks_ =
-        std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
-    CRYPTO_set_locking_callback(locking_callback);
-  }
-
-  ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
-
-private:
-  static void locking_callback(int mode, int type, const char * /*file*/,
-                               int /*line*/) {
-    auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
-    if (mode & CRYPTO_LOCK) {
-      lk.lock();
-    } else {
-      lk.unlock();
-    }
-  }
-};
-
-#endif
-
 class SSLInit {
 public:
   SSLInit() {
-#if OPENSSL_VERSION_NUMBER < 0x1010001fL
-    SSL_load_error_strings();
-    SSL_library_init();
-#else
     OPENSSL_init_ssl(
         OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
-#endif
   }
-
-  ~SSLInit() {
-#if OPENSSL_VERSION_NUMBER < 0x1010001fL
-    ERR_free_strings();
-#endif
-  }
-
-private:
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
-  SSLThreadLocks thread_init_;
-#endif
 };
 
 // SSL socket stream implementation
@@ -5395,8 +5767,8 @@ bool SSLSocketStream::is_readable() const {
 }
 
 bool SSLSocketStream::is_writable() const {
-  return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
-         0;
+  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
+         is_socket_alive(sock_);
 }
 
 ssize_t SSLSocketStream::read(char *ptr, size_t size) {
@@ -5467,6 +5839,11 @@ void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
   detail::get_remote_ip_and_port(sock_, ip, port);
 }
 
+void SSLSocketStream::get_local_ip_and_port(std::string &ip,
+                                                   int &port) const {
+  detail::get_local_ip_and_port(sock_, ip, port);
+}
+
 socket_t SSLSocketStream::socket() const { return sock_; }
 
 static SSLInit sslinit_;
@@ -5560,7 +5937,7 @@ bool SSLServer::process_and_close_socket(socket_t sock) {
       },
       [](SSL * /*ssl2*/) { return true; });
 
-  bool ret = false;
+  auto ret = false;
   if (ssl) {
     ret = detail::process_server_socket_ssl(
         svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
@@ -5654,6 +6031,11 @@ void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
   }
 }
 
+void SSLClient::load_ca_cert_store(const char *ca_cert,
+                                          std::size_t size) {
+  set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size));
+}
+
 long SSLClient::get_openssl_verify_result() const {
   return verify_result_;
 }
@@ -5738,11 +6120,16 @@ bool SSLClient::load_certs() {
         ret = false;
       }
     } else {
+      auto loaded = false;
 #ifdef _WIN32
-      detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
-#else
-      SSL_CTX_set_default_verify_paths(ctx_);
-#endif
+      loaded =
+          detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
+#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
+#if TARGET_OS_OSX
+      loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
+#endif // TARGET_OS_OSX
+#endif // _WIN32
+      if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
     }
   });
 
@@ -5776,7 +6163,7 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
             return false;
           }
 
-          auto server_cert = SSL_get_peer_certificate(ssl2);
+          auto server_cert = SSL_get1_peer_certificate(ssl2);
 
           if (server_cert == nullptr) {
             error = Error::SSLServerVerification;
@@ -5887,7 +6274,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
 
   if (alt_names) {
     auto dsn_matched = false;
-    auto ip_mached = false;
+    auto ip_matched = false;
 
     auto count = sk_GENERAL_NAME_num(alt_names);
 
@@ -5903,14 +6290,14 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
         case GEN_IPADD:
           if (!memcmp(&addr6, name, addr_len) ||
               !memcmp(&addr, name, addr_len)) {
-            ip_mached = true;
+            ip_matched = true;
           }
           break;
         }
       }
     }
 
-    if (dsn_matched || ip_mached) { ret = true; }
+    if (dsn_matched || ip_matched) { ret = true; }
   }
 
   GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
@@ -5998,13 +6385,13 @@ Client::Client(const std::string &scheme_host_port,
 
     if (is_ssl) {
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-      cli_ = detail::make_unique<SSLClient>(host, port,
-                                            client_cert_path, client_key_path);
+      cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
+                                            client_key_path);
       is_ssl_ = is_ssl;
 #endif
     } else {
-      cli_ = detail::make_unique<ClientImpl>(host, port,
-                                             client_cert_path, client_key_path);
+      cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
+                                             client_key_path);
     }
   } else {
     cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
@@ -6102,6 +6489,9 @@ Result Client::Head(const std::string &path, const Headers &headers) {
 }
 
 Result Client::Post(const std::string &path) { return cli_->Post(path); }
+Result Client::Post(const std::string &path, const Headers &headers) {
+  return cli_->Post(path, headers);
+}
 Result Client::Post(const std::string &path, const char *body,
                            size_t content_length,
                            const std::string &content_type) {
@@ -6164,6 +6554,12 @@ Result Client::Post(const std::string &path, const Headers &headers,
                            const std::string &boundary) {
   return cli_->Post(path, headers, items, boundary);
 }
+Result
+Client::Post(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items,
+             const MultipartFormDataProviderItems &provider_items) {
+  return cli_->Post(path, headers, items, provider_items);
+}
 Result Client::Put(const std::string &path) { return cli_->Put(path); }
 Result Client::Put(const std::string &path, const char *body,
                           size_t content_length,
@@ -6214,6 +6610,25 @@ Result Client::Put(const std::string &path, const Headers &headers,
                           const Params &params) {
   return cli_->Put(path, headers, params);
 }
+Result Client::Put(const std::string &path,
+                          const MultipartFormDataItems &items) {
+  return cli_->Put(path, items);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+                          const MultipartFormDataItems &items) {
+  return cli_->Put(path, headers, items);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+                          const MultipartFormDataItems &items,
+                          const std::string &boundary) {
+  return cli_->Put(path, headers, items, boundary);
+}
+Result
+Client::Put(const std::string &path, const Headers &headers,
+            const MultipartFormDataItems &items,
+            const MultipartFormDataProviderItems &provider_items) {
+  return cli_->Put(path, headers, items, provider_items);
+}
 Result Client::Patch(const std::string &path) {
   return cli_->Patch(path);
 }
@@ -6386,7 +6801,9 @@ void Client::enable_server_certificate_verification(bool enabled) {
 }
 #endif
 
-void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
+void Client::set_logger(Logger logger) {
+  cli_->set_logger(std::move(logger));
+}
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
@@ -6402,6 +6819,10 @@ void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
   }
 }
 
+void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
+  set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size));
+}
+
 long Client::get_openssl_verify_result() const {
   if (is_ssl_) {
     return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
index ce146e7..43f4379 100644 (file)
@@ -1,14 +1,14 @@
 //
 //  httplib.h
 //
-//  Copyright (c) 2022 Yuji Hirose. All rights reserved.
+//  Copyright (c) 2023 Yuji Hirose. All rights reserved.
 //  MIT License
 //
 
 #ifndef CPPHTTPLIB_HTTPLIB_H
 #define CPPHTTPLIB_HTTPLIB_H
 
-#define CPPHTTPLIB_VERSION "0.11.1"
+#define CPPHTTPLIB_VERSION "0.12.6"
 
 /*
  * Configuration
 #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
 #endif
 
+#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
+#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
+#endif
+
 #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
 #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
 #endif
@@ -168,7 +172,15 @@ using socket_t = SOCKET;
 #else // not _WIN32
 
 #include <arpa/inet.h>
+#if !defined(_AIX) && !defined(__MVS__)
 #include <ifaddrs.h>
+#endif
+#ifdef __MVS__
+#include <strings.h>
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#endif
 #include <net/if.h>
 #include <netdb.h>
 #include <netinet/in.h>
@@ -217,6 +229,7 @@ using socket_t = int;
 #include <string>
 #include <sys/stat.h>
 #include <thread>
+#include <utility>
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 #ifdef _WIN32
@@ -233,7 +246,13 @@ using socket_t = int;
 #pragma comment(lib, "crypt32.lib")
 #pragma comment(lib, "cryptui.lib")
 #endif
-#endif //_WIN32
+#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__)
+#include <TargetConditionals.h>
+#if TARGET_OS_OSX
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#endif // TARGET_OS_OSX
+#endif // _WIN32
 
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -249,14 +268,10 @@ using socket_t = int;
 
 #if OPENSSL_VERSION_NUMBER < 0x1010100fL
 #error Sorry, OpenSSL versions prior to 1.1.1 are not supported
+#elif OPENSSL_VERSION_NUMBER < 0x30000000L
+#define SSL_get1_peer_certificate SSL_get_peer_certificate
 #endif
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
-#include <openssl/crypto.h>
-inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
-  return M_ASN1_STRING_data(asn1);
-}
-#endif
 #endif
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
@@ -306,6 +321,34 @@ struct ci {
   }
 };
 
+// This is based on
+// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
+
+struct scope_exit {
+  explicit scope_exit(std::function<void(void)> &&f)
+      : exit_function(std::move(f)), execute_on_destruction{true} {}
+
+  scope_exit(scope_exit &&rhs)
+      : exit_function(std::move(rhs.exit_function)),
+        execute_on_destruction{rhs.execute_on_destruction} {
+    rhs.release();
+  }
+
+  ~scope_exit() {
+    if (execute_on_destruction) { this->exit_function(); }
+  }
+
+  void release() { this->execute_on_destruction = false; }
+
+private:
+  scope_exit(const scope_exit &) = delete;
+  void operator=(const scope_exit &) = delete;
+  scope_exit &operator=(scope_exit &&) = delete;
+
+  std::function<void(void)> exit_function;
+  bool execute_on_destruction;
+};
+
 } // namespace detail
 
 using Headers = std::multimap<std::string, std::string, detail::ci>;
@@ -338,7 +381,7 @@ public:
 
   std::function<bool(const char *data, size_t data_len)> write;
   std::function<void()> done;
-  std::function<bool()> is_writable;
+  std::function<void(const Headers &trailer)> done_with_trailer;
   std::ostream os;
 
 private:
@@ -367,6 +410,14 @@ using ContentProviderWithoutLength =
 
 using ContentProviderResourceReleaser = std::function<void(bool success)>;
 
+struct MultipartFormDataProvider {
+  std::string name;
+  ContentProviderWithoutLength provider;
+  std::string filename;
+  std::string content_type;
+};
+using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>;
+
 using ContentReceiverWithProgress =
     std::function<bool(const char *data, size_t data_length, uint64_t offset,
                        uint64_t total_length)>;
@@ -411,6 +462,8 @@ struct Request {
 
   std::string remote_addr;
   int remote_port = -1;
+  std::string local_addr;
+  int local_port = -1;
 
   // for server
   std::string version;
@@ -443,6 +496,7 @@ struct Request {
 
   bool has_file(const std::string &key) const;
   MultipartFormData get_file_value(const std::string &key) const;
+  std::vector<MultipartFormData> get_file_values(const std::string &key) const;
 
   // private members...
   size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
@@ -512,6 +566,7 @@ public:
   virtual ssize_t read(char *ptr, size_t size) = 0;
   virtual ssize_t write(const char *ptr, size_t size) = 0;
   virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
+  virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
   virtual socket_t socket() const = 0;
 
   template <typename... Args>
@@ -544,8 +599,11 @@ public:
   ~ThreadPool() override = default;
 
   void enqueue(std::function<void()> fn) override {
-    std::unique_lock<std::mutex> lock(mutex_);
-    jobs_.push_back(std::move(fn));
+    {
+      std::unique_lock<std::mutex> lock(mutex_);
+      jobs_.push_back(std::move(fn));
+    }
+
     cond_.notify_one();
   }
 
@@ -579,7 +637,7 @@ private:
 
           if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
 
-          fn = pool_.jobs_.front();
+          fn = std::move(pool_.jobs_.front());
           pool_.jobs_.pop_front();
         }
 
@@ -692,6 +750,7 @@ public:
   bool listen(const std::string &host, int port, int socket_flags = 0);
 
   bool is_running() const;
+  void wait_until_ready() const;
   void stop();
 
   std::function<TaskQueue *(void)> new_task_queue;
@@ -701,7 +760,7 @@ protected:
                        bool &connection_closed,
                        const std::function<void(Request &)> &setup_request);
 
-  std::atomic<socket_t> svr_sock_;
+  std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
   size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
   time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
   time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
@@ -753,7 +812,7 @@ private:
                                      ContentReceiver multipart_receiver);
   bool read_content_core(Stream &strm, Request &req, Response &res,
                          ContentReceiver receiver,
-                         MultipartContentHeader mulitpart_header,
+                         MultipartContentHeader multipart_header,
                          ContentReceiver multipart_receiver);
 
   virtual bool process_and_close_socket(socket_t sock);
@@ -765,7 +824,8 @@ private:
   };
   std::vector<MountPointEntry> base_dirs_;
 
-  std::atomic<bool> is_running_;
+  std::atomic<bool> is_running_{false};
+  std::atomic<bool> done_{false};
   std::map<std::string, std::string> file_extension_and_mimetype_map_;
   Handler file_request_handler_;
   Handlers get_handlers_;
@@ -807,6 +867,9 @@ enum class Error {
   UnsupportedMultipartBoundaryChars,
   Compression,
   ConnectionTimeout,
+
+  // For internal use only
+  SSLPeerCouldBeClosed_,
 };
 
 std::string to_string(const Error error);
@@ -897,6 +960,7 @@ public:
   Result Head(const std::string &path, const Headers &headers);
 
   Result Post(const std::string &path);
+  Result Post(const std::string &path, const Headers &headers);
   Result Post(const std::string &path, const char *body, size_t content_length,
               const std::string &content_type);
   Result Post(const std::string &path, const Headers &headers, const char *body,
@@ -925,6 +989,9 @@ public:
               const MultipartFormDataItems &items);
   Result Post(const std::string &path, const Headers &headers,
               const MultipartFormDataItems &items, const std::string &boundary);
+  Result Post(const std::string &path, const Headers &headers,
+              const MultipartFormDataItems &items,
+              const MultipartFormDataProviderItems &provider_items);
 
   Result Put(const std::string &path);
   Result Put(const std::string &path, const char *body, size_t content_length,
@@ -949,6 +1016,14 @@ public:
   Result Put(const std::string &path, const Params &params);
   Result Put(const std::string &path, const Headers &headers,
              const Params &params);
+  Result Put(const std::string &path, const MultipartFormDataItems &items);
+  Result Put(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items);
+  Result Put(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items, const std::string &boundary);
+  Result Put(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items,
+             const MultipartFormDataProviderItems &provider_items);
 
   Result Patch(const std::string &path);
   Result Patch(const std::string &path, const char *body, size_t content_length,
@@ -1049,6 +1124,7 @@ public:
   void set_ca_cert_path(const std::string &ca_cert_file_path,
                         const std::string &ca_cert_dir_path = std::string());
   void set_ca_cert_store(X509_STORE *ca_cert_store);
+  X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size);
 #endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -1067,8 +1143,6 @@ protected:
     bool is_open() const { return sock != INVALID_SOCKET; }
   };
 
-  Result send_(Request &&req);
-
   virtual bool create_and_connect_socket(Socket &socket, Error &error);
 
   // All of:
@@ -1090,7 +1164,7 @@ protected:
 
   void copy_settings(const ClientImpl &rhs);
 
-  // Socket endoint information
+  // Socket endpoint information
   const std::string host_;
   const int port_;
   const std::string host_and_port_;
@@ -1169,6 +1243,9 @@ protected:
   Logger logger_;
 
 private:
+  bool send_(Request &req, Response &res, Error &error);
+  Result send_(Request &&req);
+
   socket_t create_client_socket(Error &error) const;
   bool read_response_line(Stream &strm, const Request &req, Response &res);
   bool write_request(Stream &strm, Request &req, bool close_connection,
@@ -1187,6 +1264,9 @@ private:
       ContentProvider content_provider,
       ContentProviderWithoutLength content_provider_without_length,
       const std::string &content_type);
+  ContentProviderWithoutLength get_multipart_content_provider(
+      const std::string &boundary, const MultipartFormDataItems &items,
+      const MultipartFormDataProviderItems &provider_items);
 
   std::string adjust_host_string(const std::string &host) const;
 
@@ -1253,6 +1333,7 @@ public:
   Result Head(const std::string &path, const Headers &headers);
 
   Result Post(const std::string &path);
+  Result Post(const std::string &path, const Headers &headers);
   Result Post(const std::string &path, const char *body, size_t content_length,
               const std::string &content_type);
   Result Post(const std::string &path, const Headers &headers, const char *body,
@@ -1281,6 +1362,10 @@ public:
               const MultipartFormDataItems &items);
   Result Post(const std::string &path, const Headers &headers,
               const MultipartFormDataItems &items, const std::string &boundary);
+  Result Post(const std::string &path, const Headers &headers,
+              const MultipartFormDataItems &items,
+              const MultipartFormDataProviderItems &provider_items);
+
   Result Put(const std::string &path);
   Result Put(const std::string &path, const char *body, size_t content_length,
              const std::string &content_type);
@@ -1304,6 +1389,15 @@ public:
   Result Put(const std::string &path, const Params &params);
   Result Put(const std::string &path, const Headers &headers,
              const Params &params);
+  Result Put(const std::string &path, const MultipartFormDataItems &items);
+  Result Put(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items);
+  Result Put(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items, const std::string &boundary);
+  Result Put(const std::string &path, const Headers &headers,
+             const MultipartFormDataItems &items,
+             const MultipartFormDataProviderItems &provider_items);
+
   Result Patch(const std::string &path);
   Result Patch(const std::string &path, const char *body, size_t content_length,
                const std::string &content_type);
@@ -1411,6 +1505,7 @@ public:
                         const std::string &ca_cert_dir_path = std::string());
 
   void set_ca_cert_store(X509_STORE *ca_cert_store);
+  void load_ca_cert_store(const char *ca_cert, std::size_t size);
 
   long get_openssl_verify_result() const;
 
@@ -1470,6 +1565,7 @@ public:
   bool is_valid() const override;
 
   void set_ca_cert_store(X509_STORE *ca_cert_store);
+  void load_ca_cert_store(const char *ca_cert, std::size_t size);
 
   long get_openssl_verify_result() const;
 
@@ -1519,7 +1615,7 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
   auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
                   duration - std::chrono::seconds(sec))
                   .count();
-  callback(sec, usec);
+  callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
 }
 
 template <typename T>
@@ -1620,20 +1716,20 @@ Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
 
 inline std::string to_string(const Error error) {
   switch (error) {
-  case Error::Success: return "Success";
-  case Error::Connection: return "Connection";
-  case Error::BindIPAddress: return "BindIPAddress";
-  case Error::Read: return "Read";
-  case Error::Write: return "Write";
-  case Error::ExceedRedirectCount: return "ExceedRedirectCount";
-  case Error::Canceled: return "Canceled";
-  case Error::SSLConnection: return "SSLConnection";
-  case Error::SSLLoadingCerts: return "SSLLoadingCerts";
-  case Error::SSLServerVerification: return "SSLServerVerification";
+  case Error::Success: return "Success (no error)";
+  case Error::Connection: return "Could not establish connection";
+  case Error::BindIPAddress: return "Failed to bind IP address";
+  case Error::Read: return "Failed to read connection";
+  case Error::Write: return "Failed to write connection";
+  case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
+  case Error::Canceled: return "Connection handling canceled";
+  case Error::SSLConnection: return "SSL connection failed";
+  case Error::SSLLoadingCerts: return "SSL certificate loading failed";
+  case Error::SSLServerVerification: return "SSL server verification failed";
   case Error::UnsupportedMultipartBoundaryChars:
-    return "UnsupportedMultipartBoundaryChars";
-  case Error::Compression: return "Compression";
-  case Error::ConnectionTimeout: return "ConnectionTimeout";
+    return "Unsupported HTTP multipart boundary characters";
+  case Error::Compression: return "Compression failed";
+  case Error::ConnectionTimeout: return "Connection timed out";
   case Error::Unknown: return "Unknown";
   default: break;
   }
@@ -1743,6 +1839,9 @@ std::string params_to_query_str(const Params &params);
 
 void parse_query_text(const std::string &s, Params &params);
 
+bool parse_multipart_boundary(const std::string &content_type,
+                              std::string &boundary);
+
 bool parse_range_header(const std::string &s, Ranges &ranges);
 
 int close_socket(socket_t sock);
@@ -1765,6 +1864,7 @@ public:
   ssize_t read(char *ptr, size_t size) override;
   ssize_t write(const char *ptr, size_t size) override;
   void get_remote_ip_and_port(std::string &ip, int &port) const override;
+  void get_local_ip_and_port(std::string &ip, int &port) const override;
   socket_t socket() const override;
 
   const std::string &get_buffer() const;
@@ -1887,4 +1987,8 @@ private:
 
 } // namespace httplib
 
+#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL)
+#undef poll
+#endif
+
 #endif // CPPHTTPLIB_HTTPLIB_H
index 399bad9..2489e15 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * xxHash - Extremely Fast Hash algorithm
- * Copyright (C) 2020 Yann Collet
+ * Copyright (C) 2020-2021 Yann Collet
  *
  * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
  *
@@ -36,7 +36,7 @@
 /*!
  * @file xxh_x86dispatch.c
  *
- * Automatic dispatcher code for the @ref xxh3_family on x86-based targets.
+ * Automatic dispatcher code for the @ref XXH3_family on x86-based targets.
  *
  * Optional add-on.
  *
@@ -56,39 +56,42 @@ extern "C" {
 #  error "Dispatching is currently only supported on x86 and x86_64."
 #endif
 
+/*! @cond Doxygen ignores this part */
+#ifndef XXH_HAS_INCLUDE
+#  ifdef __has_include
+#    define XXH_HAS_INCLUDE(x) __has_include(x)
+#  else
+#    define XXH_HAS_INCLUDE(x) 0
+#  endif
+#endif
+/*! @endcond */
+
 /*!
  * @def XXH_X86DISPATCH_ALLOW_AVX
  * @brief Disables the AVX sanity check.
  *
- * Don't compile xxh_x86dispatch.c with options like `-mavx*`, `-march=native`,
- * or `/arch:AVX*`. It is intended to be compiled for the minimum target, and
+ * xxh_x86dispatch.c is intended to be compiled for the minimum target, and
  * it selectively enables SSE2, AVX2, and AVX512 when it is needed.
  *
- * Using this option _globally_ allows this feature, and therefore makes it
+ * Compiling with options like `-mavx*`, `-march=native`, or `/arch:AVX*`
+ * _globally_ will always enable this feature, and therefore makes it
  * undefined behavior to execute on any CPU without said feature.
  *
  * Even if the source code isn't directly using AVX intrinsics in a function,
  * the compiler can still generate AVX code from autovectorization and by
  * "upgrading" SSE2 intrinsics to use the VEX prefixes (a.k.a. AVX128).
  *
- * Use the same flags that you use to compile the rest of the program; this
- * file will safely generate SSE2, AVX2, and AVX512 without these flags.
- *
- * Define XXH_X86DISPATCH_ALLOW_AVX to ignore this check, and feel free to open
- * an issue if there is a target in the future where AVX is a default feature.
+ * Define XXH_X86DISPATCH_ALLOW_AVX to ignore this check,
+ * thus accepting that the produced binary will not work correctly
+ * on any CPU with less features than the ones stated at compilation time.
  */
 #ifdef XXH_DOXYGEN
 #  define XXH_X86DISPATCH_ALLOW_AVX
 #endif
 
 #if defined(__AVX__) && !defined(XXH_X86DISPATCH_ALLOW_AVX)
-#  error "Do not compile xxh_x86dispatch.c with AVX enabled! See the comment above."
-#endif
-
-#ifdef __has_include
-#  define XXH_HAS_INCLUDE(header) __has_include(header)
-#else
-#  define XXH_HAS_INCLUDE(header) 0
+#  error "Error: if xxh_x86dispatch.c is compiled with AVX enabled, the resulting binary will crash on sse2-only cpus !! " \
+         "If you nonetheless want to do that, please enable the XXH_X86DISPATCH_ALLOW_AVX build variable"
 #endif
 
 /*!
@@ -107,7 +110,7 @@ extern "C" {
 #ifndef XXH_DISPATCH_SCALAR
 #  if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) /* SSE2 on by default */ \
      || defined(__x86_64__) || defined(_M_X64) /* x86_64 */ \
-     || defined(__ANDROID__) || defined(__APPLEv__) /* Android or macOS */
+     || defined(__ANDROID__) || defined(__APPLE__) /* Android or macOS */
 #     define XXH_DISPATCH_SCALAR 0 /* disable */
 #  else
 #     define XXH_DISPATCH_SCALAR 1
@@ -174,6 +177,7 @@ extern "C" {
  *
  * @def XXH_TARGET_AVX512
  * @brief Like @ref XXH_TARGET_SSE2, but for AVX512.
+ *
  */
 #if defined(__GNUC__)
 #  include <emmintrin.h> /* SSE2 */
@@ -183,6 +187,18 @@ extern "C" {
 #  define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
 #  define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
 #  define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
+#elif defined(__clang__) && defined(_MSC_VER) /* clang-cl.exe */
+#  include <emmintrin.h> /* SSE2 */
+#  if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+#    include <immintrin.h> /* AVX2, AVX512F */
+#    include <smmintrin.h>
+#    include <avxintrin.h>
+#    include <avx2intrin.h>
+#    include <avx512fintrin.h>
+#  endif
+#  define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
+#  define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
+#  define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
 #elif defined(_MSC_VER)
 #  include <intrin.h>
 #  define XXH_TARGET_SSE2
@@ -192,6 +208,7 @@ extern "C" {
 #  error "Dispatching is currently not supported for your compiler."
 #endif
 
+/*! @cond Doxygen ignores this part */
 #ifdef XXH_DISPATCH_DEBUG
 /* debug logging */
 #  include <stdio.h>
@@ -201,12 +218,37 @@ extern "C" {
 #  undef NDEBUG /* avoid redefinition */
 #  define NDEBUG
 #endif
+/*! @endcond */
 #include <assert.h>
 
+#ifndef XXH_DOXYGEN
 #define XXH_INLINE_ALL
 #define XXH_X86DISPATCH
 #include "xxhash.h"
+#endif
+
+/*! @cond Doxygen ignores this part */
+#ifndef XXH_HAS_ATTRIBUTE
+#  ifdef __has_attribute
+#    define XXH_HAS_ATTRIBUTE(...) __has_attribute(__VA_ARGS__)
+#  else
+#    define XXH_HAS_ATTRIBUTE(...) 0
+#  endif
+#endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+#if XXH_HAS_ATTRIBUTE(constructor)
+#  define XXH_CONSTRUCTOR __attribute__((constructor))
+#  define XXH_DISPATCH_MAYBE_NULL 0
+#else
+#  define XXH_CONSTRUCTOR
+#  define XXH_DISPATCH_MAYBE_NULL 1
+#endif
+/*! @endcond */
 
+
+/*! @cond Doxygen ignores this part */
 /*
  * Support both AT&T and Intel dialects
  *
@@ -224,9 +266,10 @@ extern "C" {
 #else
 #  define XXH_I_ATT(intel, att) "{" att "|" intel "}\n\t"
 #endif
+/*! @endcond */
 
 /*!
- * @internal
+ * @private
  * @brief Runs CPUID.
  *
  * @param eax , ecx The parameters to pass to CPUID, %eax and %ecx respectively.
@@ -235,7 +278,7 @@ extern "C" {
 static void XXH_cpuid(xxh_u32 eax, xxh_u32 ecx, xxh_u32* abcd)
 {
 #if defined(_MSC_VER)
-    __cpuidex(abcd, eax, ecx);
+    __cpuidex((int*)abcd, eax, ecx);
 #else
     xxh_u32 ebx, edx;
 # if defined(__i386__) && defined(__PIC__)
@@ -269,7 +312,7 @@ static void XXH_cpuid(xxh_u32 eax, xxh_u32 ecx, xxh_u32* abcd)
 
 #if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
 /*!
- * @internal
+ * @private
  * @brief Runs `XGETBV`.
  *
  * While the CPU may support AVX2, the operating system might not properly save
@@ -303,20 +346,22 @@ static xxh_u64 XXH_xgetbv(void)
 }
 #endif
 
+/*! @cond Doxygen ignores this part */
 #define XXH_SSE2_CPUID_MASK (1 << 26)
 #define XXH_OSXSAVE_CPUID_MASK ((1 << 26) | (1 << 27))
 #define XXH_AVX2_CPUID_MASK (1 << 5)
 #define XXH_AVX2_XGETBV_MASK ((1 << 2) | (1 << 1))
 #define XXH_AVX512F_CPUID_MASK (1 << 16)
 #define XXH_AVX512F_XGETBV_MASK ((7 << 5) | (1 << 2) | (1 << 1))
+/*! @endcond */
 
 /*!
- * @internal
+ * @private
  * @brief Returns the best XXH3 implementation.
  *
  * Runs various CPUID/XGETBV tests to try and determine the best implementation.
  *
- * @ret The best @ref XXH_VECTOR implementation.
+ * @return The best @ref XXH_VECTOR implementation.
  * @see XXH_VECTOR_TYPES
  */
 static int XXH_featureTest(void)
@@ -441,8 +486,9 @@ static int XXH_featureTest(void)
 
 /* ===   Vector implementations   === */
 
+/*! @cond PRIVATE */
 /*!
- * @internal
+ * @private
  * @brief Defines the various dispatch functions.
  *
  * TODO: Consolidate?
@@ -450,27 +496,29 @@ static int XXH_featureTest(void)
  * @param suffix The suffix for the functions, e.g. sse2 or scalar
  * @param target XXH_TARGET_* or empty.
  */
+
 #define XXH_DEFINE_DISPATCH_FUNCS(suffix, target)                             \
                                                                               \
 /* ===   XXH3, default variants   === */                                      \
                                                                               \
 XXH_NO_INLINE target XXH64_hash_t                                             \
-XXHL64_default_##suffix(const void* XXH_RESTRICT input, size_t len)           \
+XXHL64_default_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input,          \
+                        size_t len)                                           \
 {                                                                             \
     return XXH3_hashLong_64b_internal(                                        \
                input, len, XXH3_kSecret, sizeof(XXH3_kSecret),                \
-               XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix        \
+               XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix            \
     );                                                                        \
 }                                                                             \
                                                                               \
 /* ===   XXH3, Seeded variants   === */                                       \
                                                                               \
 XXH_NO_INLINE target XXH64_hash_t                                             \
-XXHL64_seed_##suffix(const void* XXH_RESTRICT input, size_t len,              \
+XXHL64_seed_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, size_t len, \
                      XXH64_hash_t seed)                                       \
 {                                                                             \
     return XXH3_hashLong_64b_withSeed_internal(                               \
-                    input, len, seed, XXH3_accumulate_512_##suffix,           \
+                    input, len, seed, XXH3_accumulate_##suffix,               \
                     XXH3_scrambleAcc_##suffix, XXH3_initCustomSecret_##suffix \
     );                                                                        \
 }                                                                             \
@@ -478,59 +526,66 @@ XXHL64_seed_##suffix(const void* XXH_RESTRICT input, size_t len,              \
 /* ===   XXH3, Secret variants   === */                                       \
                                                                               \
 XXH_NO_INLINE target XXH64_hash_t                                             \
-XXHL64_secret_##suffix(const void* XXH_RESTRICT input, size_t len,            \
-                       const void* secret, size_t secretLen)                  \
+XXHL64_secret_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input,           \
+                       size_t len, XXH_NOESCAPE const void* secret,           \
+                       size_t secretLen)                                      \
 {                                                                             \
     return XXH3_hashLong_64b_internal(                                        \
                     input, len, secret, secretLen,                            \
-                    XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix   \
+                    XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix       \
     );                                                                        \
 }                                                                             \
                                                                               \
 /* ===   XXH3 update variants   === */                                        \
                                                                               \
 XXH_NO_INLINE target XXH_errorcode                                            \
-XXH3_update_##suffix(XXH3_state_t* state, const void* input, size_t len)      \
+XXH3_update_##suffix(XXH_NOESCAPE XXH3_state_t* state,                        \
+                     XXH_NOESCAPE const void* input, size_t len)              \
 {                                                                             \
     return XXH3_update(state, (const xxh_u8*)input, len,                      \
-                    XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
+                    XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix);     \
 }                                                                             \
                                                                               \
 /* ===   XXH128 default variants   === */                                     \
                                                                               \
 XXH_NO_INLINE target XXH128_hash_t                                            \
-XXHL128_default_##suffix(const void* XXH_RESTRICT input, size_t len)          \
+XXHL128_default_##suffix(XXH_NOESCAPE  const void* XXH_RESTRICT input,        \
+                         size_t len)                                          \
 {                                                                             \
     return XXH3_hashLong_128b_internal(                                       \
                     input, len, XXH3_kSecret, sizeof(XXH3_kSecret),           \
-                    XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix   \
+                    XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix       \
     );                                                                        \
 }                                                                             \
                                                                               \
 /* ===   XXH128 Secret variants   === */                                      \
                                                                               \
 XXH_NO_INLINE target XXH128_hash_t                                            \
-XXHL128_secret_##suffix(const void* XXH_RESTRICT input, size_t len,           \
-                        const void* XXH_RESTRICT secret, size_t secretLen)    \
+XXHL128_secret_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input,          \
+                        size_t len,                                           \
+                        XXH_NOESCAPE const void* XXH_RESTRICT secret,         \
+                        size_t secretLen)                                     \
 {                                                                             \
     return XXH3_hashLong_128b_internal(                                       \
                     input, len, (const xxh_u8*)secret, secretLen,             \
-                    XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
+                    XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix);     \
 }                                                                             \
                                                                               \
 /* ===   XXH128 Seeded variants   === */                                      \
                                                                               \
 XXH_NO_INLINE target XXH128_hash_t                                            \
-XXHL128_seed_##suffix(const void* XXH_RESTRICT input, size_t len,             \
+XXHL128_seed_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, size_t len,\
                       XXH64_hash_t seed)                                      \
 {                                                                             \
     return XXH3_hashLong_128b_withSeed_internal(input, len, seed,             \
-                    XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix,  \
+                    XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix,      \
                     XXH3_initCustomSecret_##suffix);                          \
 }
 
+/*! @endcond */
 /* End XXH_DEFINE_DISPATCH_FUNCS */
 
+/*! @cond Doxygen ignores this part */
 #if XXH_DISPATCH_SCALAR
 XXH_DEFINE_DISPATCH_FUNCS(scalar, /* nothing */)
 #endif
@@ -542,16 +597,18 @@ XXH_DEFINE_DISPATCH_FUNCS(avx2, XXH_TARGET_AVX2)
 XXH_DEFINE_DISPATCH_FUNCS(avx512, XXH_TARGET_AVX512)
 #endif
 #undef XXH_DEFINE_DISPATCH_FUNCS
+/*! @endcond */
 
 /* ====    Dispatchers    ==== */
 
-typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_default)(const void* XXH_RESTRICT, size_t);
+/*! @cond Doxygen ignores this part */
+typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_default)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t);
 
-typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSeed)(const void* XXH_RESTRICT, size_t, XXH64_hash_t);
+typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSeed)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, XXH64_hash_t);
 
-typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSecret)(const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
+typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSecret)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, XXH_NOESCAPE const void* XXH_RESTRICT, size_t);
 
-typedef XXH_errorcode (*XXH3_dispatchx86_update)(XXH3_state_t*, const void*, size_t);
+typedef XXH_errorcode (*XXH3_dispatchx86_update)(XXH_NOESCAPE XXH3_state_t*, XXH_NOESCAPE const void*, size_t);
 
 typedef struct {
     XXH3_dispatchx86_hashLong64_default    hashLong64_default;
@@ -561,9 +618,10 @@ typedef struct {
 } XXH_dispatchFunctions_s;
 
 #define XXH_NB_DISPATCHES 4
+/*! @endcond */
 
 /*!
- * @internal
+ * @private
  * @brief Table of dispatchers for @ref XXH3_64bits().
  *
  * @pre The indices must match @ref XXH_VECTOR_TYPE.
@@ -587,17 +645,18 @@ static const XXH_dispatchFunctions_s XXH_kDispatch[XXH_NB_DISPATCHES] = {
 #endif
 };
 /*!
- * @internal
+ * @private
  * @brief The selected dispatch table for @ref XXH3_64bits().
  */
 static XXH_dispatchFunctions_s XXH_g_dispatch = { NULL, NULL, NULL, NULL };
 
 
-typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_default)(const void* XXH_RESTRICT, size_t);
+/*! @cond Doxygen ignores this part */
+typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_default)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t);
 
-typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSeed)(const void* XXH_RESTRICT, size_t, XXH64_hash_t);
+typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSeed)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, XXH64_hash_t);
 
-typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSecret)(const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
+typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSecret)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
 
 typedef struct {
     XXH3_dispatchx86_hashLong128_default    hashLong128_default;
@@ -605,10 +664,11 @@ typedef struct {
     XXH3_dispatchx86_hashLong128_withSecret hashLong128_secret;
     XXH3_dispatchx86_update                 update;
 } XXH_dispatch128Functions_s;
+/*! @endcond */
 
 
 /*!
- * @internal
+ * @private
  * @brief Table of dispatchers for @ref XXH3_128bits().
  *
  * @pre The indices must match @ref XXH_VECTOR_TYPE.
@@ -633,16 +693,16 @@ static const XXH_dispatch128Functions_s XXH_kDispatch128[XXH_NB_DISPATCHES] = {
 };
 
 /*!
- * @internal
+ * @private
  * @brief The selected dispatch table for @ref XXH3_64bits().
  */
 static XXH_dispatch128Functions_s XXH_g_dispatch128 = { NULL, NULL, NULL, NULL };
 
 /*!
- * @internal
+ * @private
  * @brief Runs a CPUID check and sets the correct dispatch tables.
  */
-static void XXH_setDispatch(void)
+static XXH_CONSTRUCTOR void XXH_setDispatch(void)
 {
     int vecID = XXH_featureTest();
     XXH_STATIC_ASSERT(XXH_AVX512 == XXH_NB_DISPATCHES-1);
@@ -662,17 +722,19 @@ static void XXH_setDispatch(void)
 
 
 /* ====    XXH3 public functions    ==== */
+/*! @cond Doxygen ignores this part */
 
 static XXH64_hash_t
 XXH3_hashLong_64b_defaultSecret_selection(const void* input, size_t len,
                                           XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
 {
     (void)seed64; (void)secret; (void)secretLen;
-    if (XXH_g_dispatch.hashLong64_default == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.hashLong64_default == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch.hashLong64_default(input, len);
 }
 
-XXH64_hash_t XXH3_64bits_dispatch(const void* input, size_t len)
+XXH64_hash_t XXH3_64bits_dispatch(XXH_NOESCAPE const void* input, size_t len)
 {
     return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_defaultSecret_selection);
 }
@@ -682,11 +744,12 @@ XXH3_hashLong_64b_withSeed_selection(const void* input, size_t len,
                                      XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
 {
     (void)secret; (void)secretLen;
-    if (XXH_g_dispatch.hashLong64_seed == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.hashLong64_seed == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch.hashLong64_seed(input, len, seed64);
 }
 
-XXH64_hash_t XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
+XXH64_hash_t XXH3_64bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
 {
     return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed_selection);
 }
@@ -696,49 +759,57 @@ XXH3_hashLong_64b_withSecret_selection(const void* input, size_t len,
                                        XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
 {
     (void)seed64;
-    if (XXH_g_dispatch.hashLong64_secret == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.hashLong64_secret == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch.hashLong64_secret(input, len, secret, secretLen);
 }
 
-XXH64_hash_t XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
+XXH64_hash_t XXH3_64bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen)
 {
     return XXH3_64bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_64b_withSecret_selection);
 }
 
 XXH_errorcode
-XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
+XXH3_64bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
 {
-    if (XXH_g_dispatch.update == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.update == NULL)
+        XXH_setDispatch();
+
     return XXH_g_dispatch.update(state, (const xxh_u8*)input, len);
 }
 
+/*! @endcond */
+
 
 /* ====    XXH128 public functions    ==== */
+/*! @cond Doxygen ignores this part */
 
 static XXH128_hash_t
 XXH3_hashLong_128b_defaultSecret_selection(const void* input, size_t len,
                                            XXH64_hash_t seed64, const void* secret, size_t secretLen)
 {
     (void)seed64; (void)secret; (void)secretLen;
-    if (XXH_g_dispatch128.hashLong128_default == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.hashLong128_default == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch128.hashLong128_default(input, len);
 }
 
-XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len)
+XXH128_hash_t XXH3_128bits_dispatch(XXH_NOESCAPE const void* input, size_t len)
 {
     return XXH3_128bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_defaultSecret_selection);
 }
 
 static XXH128_hash_t
 XXH3_hashLong_128b_withSeed_selection(const void* input, size_t len,
-                                     XXH64_hash_t seed64, const void* secret, size_t secretLen)
+                                      XXH64_hash_t seed64, const void* secret, size_t secretLen)
 {
     (void)secret; (void)secretLen;
-    if (XXH_g_dispatch128.hashLong128_seed == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.hashLong128_seed == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch128.hashLong128_seed(input, len, seed64);
 }
 
-XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
+XXH128_hash_t XXH3_128bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
 {
     return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_withSeed_selection);
 }
@@ -748,22 +819,26 @@ XXH3_hashLong_128b_withSecret_selection(const void* input, size_t len,
                                         XXH64_hash_t seed64, const void* secret, size_t secretLen)
 {
     (void)seed64;
-    if (XXH_g_dispatch128.hashLong128_secret == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.hashLong128_secret == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch128.hashLong128_secret(input, len, secret, secretLen);
 }
 
-XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
+XXH128_hash_t XXH3_128bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen)
 {
     return XXH3_128bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_128b_withSecret_selection);
 }
 
 XXH_errorcode
-XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
+XXH3_128bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
 {
-    if (XXH_g_dispatch128.update == NULL) XXH_setDispatch();
+    if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.update == NULL)
+        XXH_setDispatch();
     return XXH_g_dispatch128.update(state, (const xxh_u8*)input, len);
 }
 
+/*! @endcond */
+
 #if defined (__cplusplus)
 }
 #endif
index 6bc17bc..b87cea9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * xxHash - XXH3 Dispatcher for x86-based targets
- * Copyright (C) 2020 Yann Collet
+ * Copyright (C) 2020-2021 Yann Collet
  *
  * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
  *
 extern "C" {
 #endif
 
-XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_dispatch(const void* input, size_t len);
-XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed);
-XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen);
-XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len);
+XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_dispatch(XXH_NOESCAPE const void* input, size_t len);
+XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen);
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len);
 
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len);
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed);
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen);
-XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len);
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_dispatch(XXH_NOESCAPE const void* input, size_t len);
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen);
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len);
 
 #if defined (__cplusplus)
 }
@@ -71,7 +71,6 @@ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update_dispatch(XXH3_state_t* state, c
 
 # undef  XXH128
 # define XXH128 XXH3_128bits_withSeed_dispatch
-# define XXH3_128bits XXH3_128bits_dispatch
 # undef  XXH3_128bits
 # define XXH3_128bits XXH3_128bits_dispatch
 # undef  XXH3_128bits_withSeed
index 0fae88c..083b039 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * xxHash - Extremely Fast Hash algorithm
- * Copyright (C) 2012-2020 Yann Collet
+ * Copyright (C) 2012-2021 Yann Collet
  *
  * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
  *
index 08ab794..a18e8c7 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * xxHash - Extremely Fast Hash algorithm
  * Header File
- * Copyright (C) 2012-2020 Yann Collet
+ * Copyright (C) 2012-2021 Yann Collet
  *
  * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
  *
  *   - xxHash homepage: https://www.xxhash.com
  *   - xxHash source repository: https://github.com/Cyan4973/xxHash
  */
+
 /*!
  * @mainpage xxHash
  *
+ * xxHash is an extremely fast non-cryptographic hash algorithm, working at RAM speed
+ * limits.
+ *
+ * It is proposed in four flavors, in three families:
+ * 1. @ref XXH32_family
+ *   - Classic 32-bit hash function. Simple, compact, and runs on almost all
+ *     32-bit and 64-bit systems.
+ * 2. @ref XXH64_family
+ *   - Classic 64-bit adaptation of XXH32. Just as simple, and runs well on most
+ *     64-bit systems (but _not_ 32-bit systems).
+ * 3. @ref XXH3_family
+ *   - Modern 64-bit and 128-bit hash function family which features improved
+ *     strength and performance across the board, especially on smaller data.
+ *     It benefits greatly from SIMD and 64-bit without requiring it.
+ *
+ * Benchmarks
+ * ---
+ * The reference system uses an Intel i7-9700K CPU, and runs Ubuntu x64 20.04.
+ * The open source benchmark program is compiled with clang v10.0 using -O3 flag.
+ *
+ * | Hash Name            | ISA ext | Width | Large Data Speed | Small Data Velocity |
+ * | -------------------- | ------- | ----: | ---------------: | ------------------: |
+ * | XXH3_64bits()        | @b AVX2 |    64 |        59.4 GB/s |               133.1 |
+ * | MeowHash             | AES-NI  |   128 |        58.2 GB/s |                52.5 |
+ * | XXH3_128bits()       | @b AVX2 |   128 |        57.9 GB/s |               118.1 |
+ * | CLHash               | PCLMUL  |    64 |        37.1 GB/s |                58.1 |
+ * | XXH3_64bits()        | @b SSE2 |    64 |        31.5 GB/s |               133.1 |
+ * | XXH3_128bits()       | @b SSE2 |   128 |        29.6 GB/s |               118.1 |
+ * | RAM sequential read  |         |   N/A |        28.0 GB/s |                 N/A |
+ * | ahash                | AES-NI  |    64 |        22.5 GB/s |               107.2 |
+ * | City64               |         |    64 |        22.0 GB/s |                76.6 |
+ * | T1ha2                |         |    64 |        22.0 GB/s |                99.0 |
+ * | City128              |         |   128 |        21.7 GB/s |                57.7 |
+ * | FarmHash             | AES-NI  |    64 |        21.3 GB/s |                71.9 |
+ * | XXH64()              |         |    64 |        19.4 GB/s |                71.0 |
+ * | SpookyHash           |         |    64 |        19.3 GB/s |                53.2 |
+ * | Mum                  |         |    64 |        18.0 GB/s |                67.0 |
+ * | CRC32C               | SSE4.2  |    32 |        13.0 GB/s |                57.9 |
+ * | XXH32()              |         |    32 |         9.7 GB/s |                71.9 |
+ * | City32               |         |    32 |         9.1 GB/s |                66.0 |
+ * | Blake3*              | @b AVX2 |   256 |         4.4 GB/s |                 8.1 |
+ * | Murmur3              |         |    32 |         3.9 GB/s |                56.1 |
+ * | SipHash*             |         |    64 |         3.0 GB/s |                43.2 |
+ * | Blake3*              | @b SSE2 |   256 |         2.4 GB/s |                 8.1 |
+ * | HighwayHash          |         |    64 |         1.4 GB/s |                 6.0 |
+ * | FNV64                |         |    64 |         1.2 GB/s |                62.7 |
+ * | Blake2*              |         |   256 |         1.1 GB/s |                 5.1 |
+ * | SHA1*                |         |   160 |         0.8 GB/s |                 5.6 |
+ * | MD5*                 |         |   128 |         0.6 GB/s |                 7.8 |
+ * @note
+ *   - Hashes which require a specific ISA extension are noted. SSE2 is also noted,
+ *     even though it is mandatory on x64.
+ *   - Hashes with an asterisk are cryptographic. Note that MD5 is non-cryptographic
+ *     by modern standards.
+ *   - Small data velocity is a rough average of algorithm's efficiency for small
+ *     data. For more accurate information, see the wiki.
+ *   - More benchmarks and strength tests are found on the wiki:
+ *         https://github.com/Cyan4973/xxHash/wiki
+ *
+ * Usage
+ * ------
+ * All xxHash variants use a similar API. Changing the algorithm is a trivial
+ * substitution.
+ *
+ * @pre
+ *    For functions which take an input and length parameter, the following
+ *    requirements are assumed:
+ *    - The range from [`input`, `input + length`) is valid, readable memory.
+ *      - The only exception is if the `length` is `0`, `input` may be `NULL`.
+ *    - For C++, the objects must have the *TriviallyCopyable* property, as the
+ *      functions access bytes directly as if it was an array of `unsigned char`.
+ *
+ * @anchor single_shot_example
+ * **Single Shot**
+ *
+ * These functions are stateless functions which hash a contiguous block of memory,
+ * immediately returning the result. They are the easiest and usually the fastest
+ * option.
+ *
+ * XXH32(), XXH64(), XXH3_64bits(), XXH3_128bits()
+ *
+ * @code{.c}
+ *   #include <string.h>
+ *   #include "xxhash.h"
+ *
+ *   // Example for a function which hashes a null terminated string with XXH32().
+ *   XXH32_hash_t hash_string(const char* string, XXH32_hash_t seed)
+ *   {
+ *       // NULL pointers are only valid if the length is zero
+ *       size_t length = (string == NULL) ? 0 : strlen(string);
+ *       return XXH32(string, length, seed);
+ *   }
+ * @endcode
+ *
+ * @anchor streaming_example
+ * **Streaming**
+ *
+ * These groups of functions allow incremental hashing of unknown size, even
+ * more than what would fit in a size_t.
+ *
+ * XXH32_reset(), XXH64_reset(), XXH3_64bits_reset(), XXH3_128bits_reset()
+ *
+ * @code{.c}
+ *   #include <stdio.h>
+ *   #include <assert.h>
+ *   #include "xxhash.h"
+ *   // Example for a function which hashes a FILE incrementally with XXH3_64bits().
+ *   XXH64_hash_t hashFile(FILE* f)
+ *   {
+ *       // Allocate a state struct. Do not just use malloc() or new.
+ *       XXH3_state_t* state = XXH3_createState();
+ *       assert(state != NULL && "Out of memory!");
+ *       // Reset the state to start a new hashing session.
+ *       XXH3_64bits_reset(state);
+ *       char buffer[4096];
+ *       size_t count;
+ *       // Read the file in chunks
+ *       while ((count = fread(buffer, 1, sizeof(buffer), f)) != 0) {
+ *           // Run update() as many times as necessary to process the data
+ *           XXH3_64bits_update(state, buffer, count);
+ *       }
+ *       // Retrieve the finalized hash. This will not change the state.
+ *       XXH64_hash_t result = XXH3_64bits_digest(state);
+ *       // Free the state. Do not use free().
+ *       XXH3_freeState(state);
+ *       return result;
+ *   }
+ * @endcode
+ *
  * @file xxhash.h
  * xxHash prototypes and implementation
  */
-/* TODO: update */
-/* Notice extracted from xxHash homepage:
-
-xxHash is an extremely fast hash algorithm, running at RAM speed limits.
-It also successfully passes all tests from the SMHasher suite.
-
-Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz)
-
-Name            Speed       Q.Score   Author
-xxHash          5.4 GB/s     10
-CrapWow         3.2 GB/s      2       Andrew
-MurmurHash 3a   2.7 GB/s     10       Austin Appleby
-SpookyHash      2.0 GB/s     10       Bob Jenkins
-SBox            1.4 GB/s      9       Bret Mulvey
-Lookup3         1.2 GB/s      9       Bob Jenkins
-SuperFastHash   1.2 GB/s      1       Paul Hsieh
-CityHash64      1.05 GB/s    10       Pike & Alakuijala
-FNV             0.55 GB/s     5       Fowler, Noll, Vo
-CRC32           0.43 GB/s     9
-MD5-32          0.33 GB/s    10       Ronald L. Rivest
-SHA1-32         0.28 GB/s    10
-
-Q.Score is a measure of quality of the hash function.
-It depends on successfully passing SMHasher test set.
-10 is a perfect score.
-
-Note: SMHasher's CRC32 implementation is not the fastest one.
-Other speed-oriented implementations can be faster,
-especially in combination with PCLMUL instruction:
-https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html?showComment=1552696407071#c3490092340461170735
-
-A 64-bit version, named XXH64, is available since r35.
-It offers much better speed, but for 64-bit applications only.
-Name     Speed on 64 bits    Speed on 32 bits
-XXH64       13.8 GB/s            1.9 GB/s
-XXH32        6.8 GB/s            6.0 GB/s
-*/
 
 #if defined (__cplusplus)
 extern "C" {
@@ -84,21 +177,80 @@ extern "C" {
  *  INLINE mode
  ******************************/
 /*!
- * XXH_INLINE_ALL (and XXH_PRIVATE_API)
+ * @defgroup public Public API
+ * Contains details on the public xxHash functions.
+ * @{
+ */
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Gives access to internal state declaration, required for static allocation.
+ *
+ * Incompatible with dynamic linking, due to risks of ABI changes.
+ *
+ * Usage:
+ * @code{.c}
+ *     #define XXH_STATIC_LINKING_ONLY
+ *     #include "xxhash.h"
+ * @endcode
+ */
+#  define XXH_STATIC_LINKING_ONLY
+/* Do not undef XXH_STATIC_LINKING_ONLY for Doxygen */
+
+/*!
+ * @brief Gives access to internal definitions.
+ *
+ * Usage:
+ * @code{.c}
+ *     #define XXH_STATIC_LINKING_ONLY
+ *     #define XXH_IMPLEMENTATION
+ *     #include "xxhash.h"
+ * @endcode
+ */
+#  define XXH_IMPLEMENTATION
+/* Do not undef XXH_IMPLEMENTATION for Doxygen */
+
+/*!
+ * @brief Exposes the implementation and marks all functions as `inline`.
+ *
  * Use these build macros to inline xxhash into the target unit.
  * Inlining improves performance on small inputs, especially when the length is
  * expressed as a compile-time constant:
  *
- *      https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html
+ *  https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html
  *
  * It also keeps xxHash symbols private to the unit, so they are not exported.
  *
  * Usage:
+ * @code{.c}
  *     #define XXH_INLINE_ALL
  *     #include "xxhash.h"
- *
+ * @endcode
  * Do not compile and link xxhash.o as a separate object, as it is not useful.
  */
+#  define XXH_INLINE_ALL
+#  undef XXH_INLINE_ALL
+/*!
+ * @brief Exposes the implementation without marking functions as inline.
+ */
+#  define XXH_PRIVATE_API
+#  undef XXH_PRIVATE_API
+/*!
+ * @brief Emulate a namespace by transparently prefixing all symbols.
+ *
+ * If you want to include _and expose_ xxHash functions from within your own
+ * library, but also want to avoid symbol collisions with other libraries which
+ * may also include xxHash, you can use @ref XXH_NAMESPACE to automatically prefix
+ * any public symbol from xxhash library with the value of @ref XXH_NAMESPACE
+ * (therefore, avoid empty or numeric values).
+ *
+ * Note that no change is required within the calling program as long as it
+ * includes `xxhash.h`: Regular symbol names will be automatically translated
+ * by this header.
+ */
+#  define XXH_NAMESPACE /* YOUR NAME HERE */
+#  undef XXH_NAMESPACE
+#endif
+
 #if (defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)) \
     && !defined(XXH_INLINE_ALL_31684351384)
    /* this section should be traversed only once */
@@ -213,21 +365,13 @@ extern "C" {
 #  undef XXHASH_H_STATIC_13879238742
 #endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */
 
-
-
 /* ****************************************************************
  *  Stable API
  *****************************************************************/
 #ifndef XXHASH_H_5627135585666179
 #define XXHASH_H_5627135585666179 1
 
-
-/*!
- * @defgroup public Public API
- * Contains details on the public xxHash functions.
- * @{
- */
-/* specific declaration modes for Windows */
+/*! @brief Marks a global symbol. */
 #if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
 #  if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
 #    ifdef XXH_EXPORT
@@ -240,24 +384,6 @@ extern "C" {
 #  endif
 #endif
 
-#ifdef XXH_DOXYGEN
-/*!
- * @brief Emulate a namespace by transparently prefixing all symbols.
- *
- * If you want to include _and expose_ xxHash functions from within your own
- * library, but also want to avoid symbol collisions with other libraries which
- * may also include xxHash, you can use XXH_NAMESPACE to automatically prefix
- * any public symbol from xxhash library with the value of XXH_NAMESPACE
- * (therefore, avoid empty or numeric values).
- *
- * Note that no change is required within the calling program as long as it
- * includes `xxhash.h`: Regular symbol names will be automatically translated
- * by this header.
- */
-#  define XXH_NAMESPACE /* YOUR NAME HERE */
-#  undef XXH_NAMESPACE
-#endif
-
 #ifdef XXH_NAMESPACE
 #  define XXH_CAT(A,B) A##B
 #  define XXH_NAME2(A,B) XXH_CAT(A,B)
@@ -318,11 +444,39 @@ extern "C" {
 
 
 /* *************************************
+*  Compiler specifics
+***************************************/
+
+/* specific declaration modes for Windows */
+#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
+#  if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
+#    ifdef XXH_EXPORT
+#      define XXH_PUBLIC_API __declspec(dllexport)
+#    elif XXH_IMPORT
+#      define XXH_PUBLIC_API __declspec(dllimport)
+#    endif
+#  else
+#    define XXH_PUBLIC_API   /* do nothing */
+#  endif
+#endif
+
+#if defined (__GNUC__)
+# define XXH_CONSTF  __attribute__((const))
+# define XXH_PUREF   __attribute__((pure))
+# define XXH_MALLOCF __attribute__((malloc))
+#else
+# define XXH_CONSTF  /* disable */
+# define XXH_PUREF
+# define XXH_MALLOCF
+#endif
+
+/* *************************************
 *  Version
 ***************************************/
 #define XXH_VERSION_MAJOR    0
 #define XXH_VERSION_MINOR    8
-#define XXH_VERSION_RELEASE  1
+#define XXH_VERSION_RELEASE  2
+/*! @brief Version number, encoded as two digits each */
 #define XXH_VERSION_NUMBER  (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)
 
 /*!
@@ -331,16 +485,22 @@ extern "C" {
  * This is mostly useful when xxHash is compiled as a shared library,
  * since the returned value comes from the library, as opposed to header file.
  *
- * @return `XXH_VERSION_NUMBER` of the invoked library.
+ * @return @ref XXH_VERSION_NUMBER of the invoked library.
  */
-XXH_PUBLIC_API unsigned XXH_versionNumber (void);
+XXH_PUBLIC_API XXH_CONSTF unsigned XXH_versionNumber (void);
 
 
 /* ****************************
 *  Common basic types
 ******************************/
 #include <stddef.h>   /* size_t */
-typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode;
+/*!
+ * @brief Exit code for the streaming API.
+ */
+typedef enum {
+    XXH_OK = 0, /*!< OK */
+    XXH_ERROR   /*!< Error */
+} XXH_errorcode;
 
 
 /*-**********************************************************************
@@ -364,29 +524,27 @@ typedef uint32_t XXH32_hash_t;
 #   include <limits.h>
 #   if UINT_MAX == 0xFFFFFFFFUL
       typedef unsigned int XXH32_hash_t;
+#   elif ULONG_MAX == 0xFFFFFFFFUL
+      typedef unsigned long XXH32_hash_t;
 #   else
-#     if ULONG_MAX == 0xFFFFFFFFUL
-        typedef unsigned long XXH32_hash_t;
-#     else
-#       error "unsupported platform: need a 32-bit type"
-#     endif
+#     error "unsupported platform: need a 32-bit type"
 #   endif
 #endif
 
 /*!
  * @}
  *
- * @defgroup xxh32_family XXH32 family
+ * @defgroup XXH32_family XXH32 family
  * @ingroup public
  * Contains functions used in the classic 32-bit xxHash algorithm.
  *
  * @note
  *   XXH32 is useful for older platforms, with no or poor 64-bit performance.
- *   Note that @ref xxh3_family provides competitive speed
- *   for both 32-bit and 64-bit systems, and offers true 64/128 bit hash results.
+ *   Note that the @ref XXH3_family provides competitive speed for both 32-bit
+ *   and 64-bit systems, and offers true 64/128 bit hash results.
  *
- * @see @ref xxh64_family, @ref xxh3_family : Other xxHash families
- * @see @ref xxh32_impl for implementation details
+ * @see @ref XXH64_family, @ref XXH3_family : Other xxHash families
+ * @see @ref XXH32_impl for implementation details
  * @{
  */
 
@@ -395,6 +553,8 @@ typedef uint32_t XXH32_hash_t;
  *
  * Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark): 5.4 GB/s
  *
+ * See @ref single_shot_example "Single Shot Example" for an example.
+ *
  * @param input The block of data to be hashed, at least @p length bytes in size.
  * @param length The length of @p input, in bytes.
  * @param seed The 32-bit seed to alter the hash's output predictably.
@@ -412,8 +572,9 @@ typedef uint32_t XXH32_hash_t;
  * @see
  *    XXH32_createState(), XXH32_update(), XXH32_digest(): Streaming version.
  */
-XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
 
+#ifndef XXH_NO_STREAM
 /*!
  * Streaming functions generate the xxHash value from an incremental input.
  * This method is slower than single-call functions, due to state management.
@@ -436,32 +597,7 @@ XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_
  *
  * When done, release the state using `XXH*_freeState()`.
  *
- * Example code for incrementally hashing a file:
- * @code{.c}
- *    #include <stdio.h>
- *    #include <xxhash.h>
- *    #define BUFFER_SIZE 256
- *
- *    // Note: XXH64 and XXH3 use the same interface.
- *    XXH32_hash_t
- *    hashFile(FILE* stream)
- *    {
- *        XXH32_state_t* state;
- *        unsigned char buf[BUFFER_SIZE];
- *        size_t amt;
- *        XXH32_hash_t hash;
- *
- *        state = XXH32_createState();       // Create a state
- *        assert(state != NULL);             // Error check here
- *        XXH32_reset(state, 0xbaad5eed);    // Reset state with our seed
- *        while ((amt = fread(buf, 1, sizeof(buf), stream)) != 0) {
- *            XXH32_update(state, buf, amt); // Hash the file in chunks
- *        }
- *        hash = XXH32_digest(state);        // Finalize the hash
- *        XXH32_freeState(state);            // Clean up
- *        return hash;
- *    }
- * @endcode
+ * @see streaming_example at the top of @ref xxhash.h for an example.
  */
 
 /*!
@@ -478,7 +614,7 @@ typedef struct XXH32_state_s XXH32_state_t;
  * Must be freed with XXH32_freeState().
  * @return An allocated XXH32_state_t on success, `NULL` on failure.
  */
-XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void);
+XXH_PUBLIC_API XXH_MALLOCF XXH32_state_t* XXH32_createState(void);
 /*!
  * @brief Frees an @ref XXH32_state_t.
  *
@@ -546,7 +682,8 @@ XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void*
  *
  * @return The calculated xxHash32 value from that state.
  */
-XXH_PUBLIC_API XXH32_hash_t  XXH32_digest (const XXH32_state_t* statePtr);
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
 
 /*******   Canonical representation   *******/
 
@@ -597,43 +734,72 @@ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t
  *
  * @return The converted hash.
  */
-XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
 
 
+/*! @cond Doxygen ignores this part */
 #ifdef __has_attribute
 # define XXH_HAS_ATTRIBUTE(x) __has_attribute(x)
 #else
 # define XXH_HAS_ATTRIBUTE(x) 0
 #endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+/*
+ * C23 __STDC_VERSION__ number hasn't been specified yet. For now
+ * leave as `201711L` (C17 + 1).
+ * TODO: Update to correct value when its been specified.
+ */
+#define XXH_C23_VN 201711L
+/*! @endcond */
 
+/*! @cond Doxygen ignores this part */
 /* C-language Attributes are added in C23. */
-#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute)
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= XXH_C23_VN) && defined(__has_c_attribute)
 # define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
 #else
 # define XXH_HAS_C_ATTRIBUTE(x) 0
 #endif
+/*! @endcond */
 
+/*! @cond Doxygen ignores this part */
 #if defined(__cplusplus) && defined(__has_cpp_attribute)
 # define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
 #else
 # define XXH_HAS_CPP_ATTRIBUTE(x) 0
 #endif
+/*! @endcond */
 
+/*! @cond Doxygen ignores this part */
 /*
-Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute
-introduced in CPP17 and C23.
-CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough
-C23   : https://en.cppreference.com/w/c/language/attributes/fallthrough
-*/
-#if XXH_HAS_C_ATTRIBUTE(x)
-# define XXH_FALLTHROUGH [[fallthrough]]
-#elif XXH_HAS_CPP_ATTRIBUTE(x)
+ * Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute
+ * introduced in CPP17 and C23.
+ * CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough
+ * C23   : https://en.cppreference.com/w/c/language/attributes/fallthrough
+ */
+#if XXH_HAS_C_ATTRIBUTE(fallthrough) || XXH_HAS_CPP_ATTRIBUTE(fallthrough)
 # define XXH_FALLTHROUGH [[fallthrough]]
 #elif XXH_HAS_ATTRIBUTE(__fallthrough__)
-# define XXH_FALLTHROUGH __attribute__ ((fallthrough))
+# define XXH_FALLTHROUGH __attribute__ ((__fallthrough__))
+#else
+# define XXH_FALLTHROUGH /* fallthrough */
+#endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+/*
+ * Define XXH_NOESCAPE for annotated pointers in public API.
+ * https://clang.llvm.org/docs/AttributeReference.html#noescape
+ * As of writing this, only supported by clang.
+ */
+#if XXH_HAS_ATTRIBUTE(noescape)
+# define XXH_NOESCAPE __attribute__((noescape))
 #else
-# define XXH_FALLTHROUGH
+# define XXH_NOESCAPE
 #endif
+/*! @endcond */
+
 
 /*!
  * @}
@@ -671,7 +837,7 @@ typedef uint64_t XXH64_hash_t;
 /*!
  * @}
  *
- * @defgroup xxh64_family XXH64 family
+ * @defgroup XXH64_family XXH64 family
  * @ingroup public
  * @{
  * Contains functions used in the classic 64-bit xxHash algorithm.
@@ -682,7 +848,6 @@ typedef uint64_t XXH64_hash_t;
  *   It provides better speed for systems with vector processing capabilities.
  */
 
-
 /*!
  * @brief Calculates the 64-bit hash of @p input using xxHash64.
  *
@@ -706,32 +871,131 @@ typedef uint64_t XXH64_hash_t;
  * @see
  *    XXH64_createState(), XXH64_update(), XXH64_digest(): Streaming version.
  */
-XXH_PUBLIC_API XXH64_hash_t XXH64(const void* input, size_t length, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed);
 
 /*******   Streaming   *******/
+#ifndef XXH_NO_STREAM
 /*!
  * @brief The opaque state struct for the XXH64 streaming API.
  *
  * @see XXH64_state_s for details.
  */
 typedef struct XXH64_state_s XXH64_state_t;   /* incomplete type */
-XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void);
+
+/*!
+ * @brief Allocates an @ref XXH64_state_t.
+ *
+ * Must be freed with XXH64_freeState().
+ * @return An allocated XXH64_state_t on success, `NULL` on failure.
+ */
+XXH_PUBLIC_API XXH_MALLOCF XXH64_state_t* XXH64_createState(void);
+
+/*!
+ * @brief Frees an @ref XXH64_state_t.
+ *
+ * Must be allocated with XXH64_createState().
+ * @param statePtr A pointer to an @ref XXH64_state_t allocated with @ref XXH64_createState().
+ * @return XXH_OK.
+ */
 XXH_PUBLIC_API XXH_errorcode  XXH64_freeState(XXH64_state_t* statePtr);
-XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state);
 
-XXH_PUBLIC_API XXH_errorcode XXH64_reset  (XXH64_state_t* statePtr, XXH64_hash_t seed);
-XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length);
-XXH_PUBLIC_API XXH64_hash_t  XXH64_digest (const XXH64_state_t* statePtr);
+/*!
+ * @brief Copies one @ref XXH64_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ *   @p dst_state and @p src_state must not be `NULL` and must not overlap.
+ */
+XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dst_state, const XXH64_state_t* src_state);
+
+/*!
+ * @brief Resets an @ref XXH64_state_t to begin a new hash.
+ *
+ * This function resets and seeds a state. Call it before @ref XXH64_update().
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH64_reset  (XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH64_state_t.
+ *
+ * Call this to incrementally consume blocks of data.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ * @pre
+ *   The memory between @p input and @p input + @p length must be valid,
+ *   readable, contiguous memory. However, if @p length is `0`, @p input may be
+ *   `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH_NOESCAPE XXH64_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length);
 
+/*!
+ * @brief Returns the calculated hash value from an @ref XXH64_state_t.
+ *
+ * @note
+ *   Calling XXH64_digest() will not affect @p statePtr, so you can update,
+ *   digest, and update again.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ *  @p statePtr must not be `NULL`.
+ *
+ * @return The calculated xxHash64 value from that state.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_digest (XXH_NOESCAPE const XXH64_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
 /*******   Canonical representation   *******/
+
+/*!
+ * @brief Canonical (big endian) representation of @ref XXH64_hash_t.
+ */
 typedef struct { unsigned char digest[sizeof(XXH64_hash_t)]; } XXH64_canonical_t;
-XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash);
-XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src);
+
+/*!
+ * @brief Converts an @ref XXH64_hash_t to a big endian @ref XXH64_canonical_t.
+ *
+ * @param dst The @ref XXH64_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH64_hash_t to be converted.
+ *
+ * @pre
+ *   @p dst must not be `NULL`.
+ */
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH64_canonical_t to a native @ref XXH64_hash_t.
+ *
+ * @param src The @ref XXH64_canonical_t to convert.
+ *
+ * @pre
+ *   @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src);
+
+#ifndef XXH_NO_XXH3
 
 /*!
  * @}
  * ************************************************************************
- * @defgroup xxh3_family XXH3 family
+ * @defgroup XXH3_family XXH3 family
  * @ingroup public
  * @{
  *
@@ -751,16 +1015,26 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src
  *
  * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic,
  * but does not require it.
- * Any 32-bit and 64-bit targets that can run XXH32 smoothly
- * can run XXH3 at competitive speeds, even without vector support.
- * Further details are explained in the implementation.
- *
- * Optimized implementations are provided for AVX512, AVX2, SSE2, NEON, POWER8,
- * ZVector and scalar targets. This can be controlled via the XXH_VECTOR macro.
+ * Most 32-bit and 64-bit targets that can run XXH32 smoothly can run XXH3
+ * at competitive speeds, even without vector support. Further details are
+ * explained in the implementation.
+ *
+ * XXH3 has a fast scalar implementation, but it also includes accelerated SIMD
+ * implementations for many common platforms:
+ *   - AVX512
+ *   - AVX2
+ *   - SSE2
+ *   - ARM NEON
+ *   - WebAssembly SIMD128
+ *   - POWER8 VSX
+ *   - s390x ZVector
+ * This can be controlled via the @ref XXH_VECTOR macro, but it automatically
+ * selects the best version according to predefined macros. For the x86 family, an
+ * automatic runtime dispatcher is included separately in @ref xxh_x86dispatch.c.
  *
  * XXH3 implementation is portable:
  * it has a generic C90 formulation that can be compiled on any platform,
- * all implementations generage exactly the same hash value on all platforms.
+ * all implementations generate exactly the same hash value on all platforms.
  * Starting from v0.8.0, it's also labelled "stable", meaning that
  * any future version will also generate the same hash value.
  *
@@ -772,24 +1046,42 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src
  *
  * The API supports one-shot hashing, streaming mode, and custom secrets.
  */
-
 /*-**********************************************************************
 *  XXH3 64-bit variant
 ************************************************************************/
 
-/* XXH3_64bits():
- * default 64-bit variant, using default secret and default seed of 0.
- * It's the fastest variant. */
-XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* data, size_t len);
+/*!
+ * @brief 64-bit unseeded variant of XXH3.
+ *
+ * This is equivalent to @ref XXH3_64bits_withSeed() with a seed of 0, however
+ * it may have slightly better performance due to constant propagation of the
+ * defaults.
+ *
+ * @see
+ *    XXH32(), XXH64(), XXH3_128bits(): equivalent for the other xxHash algorithms
+ * @see
+ *    XXH3_64bits_withSeed(), XXH3_64bits_withSecret(): other seeding variants
+ * @see
+ *    XXH3_64bits_reset(), XXH3_64bits_update(), XXH3_64bits_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length);
 
-/*
- * XXH3_64bits_withSeed():
- * This variant generates a custom secret on the fly
- * based on default secret altered using the `seed` value.
+/*!
+ * @brief 64-bit seeded variant of XXH3
+ *
+ * This variant generates a custom secret on the fly based on default secret
+ * altered using the `seed` value.
+ *
  * While this operation is decently fast, note that it's not completely free.
- * Note: seed==0 produces the same results as XXH3_64bits().
+ *
+ * @note
+ *    seed == 0 produces the same results as @ref XXH3_64bits().
+ *
+ * @param input The data to hash
+ * @param length The length
+ * @param seed The 64-bit seed to alter the state.
  */
-XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed);
 
 /*!
  * The bare minimum size for a custom secret.
@@ -800,8 +1092,9 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, X
  */
 #define XXH3_SECRET_SIZE_MIN 136
 
-/*
- * XXH3_64bits_withSecret():
+/*!
+ * @brief 64-bit variant of XXH3 with a custom "secret".
+ *
  * It's possible to provide any blob of bytes as a "secret" to generate the hash.
  * This makes it more difficult for an external actor to prepare an intentional collision.
  * The main condition is that secretSize *must* be large enough (>= XXH3_SECRET_SIZE_MIN).
@@ -817,10 +1110,11 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, X
  * This is not necessarily the case when using the blob of bytes directly
  * because, when hashing _small_ inputs, only a portion of the secret is employed.
  */
-XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize);
 
 
 /*******   Streaming   *******/
+#ifndef XXH_NO_STREAM
 /*
  * Streaming requires state maintenance.
  * This operation costs memory and CPU.
@@ -834,23 +1128,53 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len,
  * @see XXH3_state_s for details.
  */
 typedef struct XXH3_state_s XXH3_state_t;
-XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void);
+XXH_PUBLIC_API XXH_MALLOCF XXH3_state_t* XXH3_createState(void);
 XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr);
-XXH_PUBLIC_API void XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state);
 
-/*
- * XXH3_64bits_reset():
- * Initialize with default parameters.
- * digest will be equivalent to `XXH3_64bits()`.
+/*!
+ * @brief Copies one @ref XXH3_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ *   @p dst_state and @p src_state must not be `NULL` and must not overlap.
  */
-XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH3_state_t* statePtr);
-/*
- * XXH3_64bits_reset_withSeed():
- * Generate a custom secret from `seed`, and store it into `statePtr`.
- * digest will be equivalent to `XXH3_64bits_withSeed()`.
+XXH_PUBLIC_API void XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t to begin a new hash.
+ *
+ * This function resets `statePtr` and generate a secret with default parameters. Call it before @ref XXH3_64bits_update().
+ * Digest will be equivalent to `XXH3_64bits()`.
+ *
+ * @param statePtr The state struct to reset.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ *
  */
-XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed);
-/*
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash.
+ *
+ * This function resets `statePtr` and generate a secret from `seed`. Call it before @ref XXH3_64bits_update().
+ * Digest will be equivalent to `XXH3_64bits_withSeed()`.
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed     The 64-bit seed to alter the state.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed);
+
+/*!
  * XXH3_64bits_reset_withSecret():
  * `secret` is referenced, it _must outlive_ the hash streaming session.
  * Similar to one-shot API, `secretSize` must be >= `XXH3_SECRET_SIZE_MIN`,
@@ -859,53 +1183,172 @@ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr,
  * When in doubt about the randomness of a candidate `secret`,
  * consider employing `XXH3_generateSecret()` instead (see below).
  */
-XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize);
-
-XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH3_state_t* statePtr, const void* input, size_t length);
-XXH_PUBLIC_API XXH64_hash_t  XXH3_64bits_digest (const XXH3_state_t* statePtr);
-
-/* note : canonical representation of XXH3 is the same as XXH64
- * since they both produce XXH64_hash_t values */
-
-
-/*-**********************************************************************
-*  XXH3 128-bit variant
-************************************************************************/
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH3_state_t.
+ *
+ * Call this to incrementally consume blocks of data.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ * @pre
+ *   The memory between @p input and @p input + @p length must be valid,
+ *   readable, contiguous memory. However, if @p length is `0`, @p input may be
+ *   `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated XXH3 64-bit hash value from an @ref XXH3_state_t.
+ *
+ * @note
+ *   Calling XXH3_64bits_digest() will not affect @p statePtr, so you can update,
+ *   digest, and update again.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ *  @p statePtr must not be `NULL`.
+ *
+ * @return The calculated XXH3 64-bit hash value from that state.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t  XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/* note : canonical representation of XXH3 is the same as XXH64
+ * since they both produce XXH64_hash_t values */
+
+
+/*-**********************************************************************
+*  XXH3 128-bit variant
+************************************************************************/
+
+/*!
+ * @brief The return value from 128-bit hashes.
+ *
+ * Stored in little endian order, although the fields themselves are in native
+ * endianness.
+ */
+typedef struct {
+    XXH64_hash_t low64;   /*!< `value & 0xFFFFFFFFFFFFFFFF` */
+    XXH64_hash_t high64;  /*!< `value >> 64` */
+} XXH128_hash_t;
+
+/*!
+ * @brief Unseeded 128-bit variant of XXH3
+ *
+ * The 128-bit variant of XXH3 has more strength, but it has a bit of overhead
+ * for shorter inputs.
+ *
+ * This is equivalent to @ref XXH3_128bits_withSeed() with a seed of 0, however
+ * it may have slightly better performance due to constant propagation of the
+ * defaults.
+ *
+ * @see
+ *    XXH32(), XXH64(), XXH3_64bits(): equivalent for the other xxHash algorithms
+ * @see
+ *    XXH3_128bits_withSeed(), XXH3_128bits_withSecret(): other seeding variants
+ * @see
+ *    XXH3_128bits_reset(), XXH3_128bits_update(), XXH3_128bits_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* data, size_t len);
+/*! @brief Seeded 128-bit variant of XXH3. @see XXH3_64bits_withSeed(). */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSeed(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed);
+/*! @brief Custom secret 128-bit variant of XXH3. @see XXH3_64bits_withSecret(). */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+/*******   Streaming   *******/
+#ifndef XXH_NO_STREAM
+/*
+ * Streaming requires state maintenance.
+ * This operation costs memory and CPU.
+ * As a consequence, streaming is slower than one-shot hashing.
+ * For better performance, prefer one-shot functions whenever applicable.
+ *
+ * XXH3_128bits uses the same XXH3_state_t as XXH3_64bits().
+ * Use already declared XXH3_createState() and XXH3_freeState().
+ *
+ * All reset and streaming functions have same meaning as their 64-bit counterpart.
+ */
+
+/*!
+ * @brief Resets an @ref XXH3_state_t to begin a new hash.
+ *
+ * This function resets `statePtr` and generate a secret with default parameters. Call it before @ref XXH3_128bits_update().
+ * Digest will be equivalent to `XXH3_128bits()`.
+ *
+ * @param statePtr The state struct to reset.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash.
+ *
+ * This function resets `statePtr` and generate a secret from `seed`. Call it before @ref XXH3_128bits_update().
+ * Digest will be equivalent to `XXH3_128bits_withSeed()`.
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed     The 64-bit seed to alter the state.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed);
+/*! @brief Custom secret 128-bit variant of XXH3. @see XXH_64bits_reset_withSecret(). */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH3_state_t.
+ *
+ * Call this to incrementally consume blocks of data.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ *   @p statePtr must not be `NULL`.
+ * @pre
+ *   The memory between @p input and @p input + @p length must be valid,
+ *   readable, contiguous memory. However, if @p length is `0`, @p input may be
+ *   `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length);
 
 /*!
- * @brief The return value from 128-bit hashes.
+ * @brief Returns the calculated XXH3 128-bit hash value from an @ref XXH3_state_t.
  *
- * Stored in little endian order, although the fields themselves are in native
- * endianness.
- */
-typedef struct {
-    XXH64_hash_t low64;   /*!< `value & 0xFFFFFFFFFFFFFFFF` */
-    XXH64_hash_t high64;  /*!< `value >> 64` */
-} XXH128_hash_t;
-
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* data, size_t len);
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed(const void* data, size_t len, XXH64_hash_t seed);
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
-
-/*******   Streaming   *******/
-/*
- * Streaming requires state maintenance.
- * This operation costs memory and CPU.
- * As a consequence, streaming is slower than one-shot hashing.
- * For better performance, prefer one-shot functions whenever applicable.
+ * @note
+ *   Calling XXH3_128bits_digest() will not affect @p statePtr, so you can update,
+ *   digest, and update again.
  *
- * XXH3_128bits uses the same XXH3_state_t as XXH3_64bits().
- * Use already declared XXH3_createState() and XXH3_freeState().
+ * @param statePtr The state struct to calculate the hash from.
  *
- * All reset and streaming functions have same meaning as their 64-bit counterpart.
+ * @pre
+ *  @p statePtr must not be `NULL`.
+ *
+ * @return The calculated XXH3 128-bit hash value from that state.
  */
-
-XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH3_state_t* statePtr);
-XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed);
-XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize);
-
-XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH3_state_t* statePtr, const void* input, size_t length);
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* statePtr);
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
 
 /* Following helper functions make it possible to compare XXH128_hast_t values.
  * Since XXH128_hash_t is a structure, this capability is not offered by the language.
@@ -915,26 +1358,48 @@ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* statePtr);
  * XXH128_isEqual():
  * Return: 1 if `h1` and `h2` are equal, 0 if they are not.
  */
-XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2);
+XXH_PUBLIC_API XXH_PUREF int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2);
 
 /*!
- * XXH128_cmp():
- *
+ * @brief Compares two @ref XXH128_hash_t
  * This comparator is compatible with stdlib's `qsort()`/`bsearch()`.
  *
- * return: >0 if *h128_1  > *h128_2
- *         =0 if *h128_1 == *h128_2
- *         <0 if *h128_1  < *h128_2
+ * @return: >0 if *h128_1  > *h128_2
+ *          =0 if *h128_1 == *h128_2
+ *          <0 if *h128_1  < *h128_2
  */
-XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2);
+XXH_PUBLIC_API XXH_PUREF int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2);
 
 
 /*******   Canonical representation   *******/
 typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t;
-XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash);
-XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src);
 
 
+/*!
+ * @brief Converts an @ref XXH128_hash_t to a big endian @ref XXH128_canonical_t.
+ *
+ * @param dst The @ref XXH128_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH128_hash_t to be converted.
+ *
+ * @pre
+ *   @p dst must not be `NULL`.
+ */
+XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH128_canonical_t to a native @ref XXH128_hash_t.
+ *
+ * @param src The @ref XXH128_canonical_t to convert.
+ *
+ * @pre
+ *   @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src);
+
+
+#endif  /* !XXH_NO_XXH3 */
 #endif  /* XXH_NO_LONG_LONG */
 
 /*!
@@ -978,7 +1443,7 @@ struct XXH32_state_s {
    XXH32_hash_t v[4];         /*!< Accumulator lanes */
    XXH32_hash_t mem32[4];     /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */
    XXH32_hash_t memsize;      /*!< Amount of data in @ref mem32 */
-   XXH32_hash_t reserved;     /*!< Reserved field. Do not read or write to it, it may be removed. */
+   XXH32_hash_t reserved;     /*!< Reserved field. Do not read nor write to it. */
 };   /* typedef'd to XXH32_state_t */
 
 
@@ -1002,9 +1467,11 @@ struct XXH64_state_s {
    XXH64_hash_t mem64[4];     /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */
    XXH32_hash_t memsize;      /*!< Amount of data in @ref mem64 */
    XXH32_hash_t reserved32;   /*!< Reserved field, needed for padding anyways*/
-   XXH64_hash_t reserved64;   /*!< Reserved field. Do not read or write to it, it may be removed. */
+   XXH64_hash_t reserved64;   /*!< Reserved field. Do not read or write to it. */
 };   /* typedef'd to XXH64_state_t */
 
+#ifndef XXH_NO_XXH3
+
 #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* >= C11 */
 #  include <stdalign.h>
 #  define XXH_ALIGN(n)      alignas(n)
@@ -1038,6 +1505,7 @@ struct XXH64_state_s {
 #define XXH3_INTERNALBUFFER_SIZE 256
 
 /*!
+ * @internal
  * @brief Default size of the secret buffer (and @ref XXH3_kSecret).
  *
  * This is the size used in @ref XXH3_kSecret and the seeded functions.
@@ -1070,7 +1538,7 @@ struct XXH64_state_s {
  */
 struct XXH3_state_s {
    XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]);
-       /*!< The 8 accumulators. Similar to `vN` in @ref XXH32_state_s::v1 and @ref XXH64_state_s */
+       /*!< The 8 accumulators. See @ref XXH32_state_s::v and @ref XXH64_state_s::v */
    XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]);
        /*!< Used to store a custom secret generated from a seed. */
    XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]);
@@ -1110,69 +1578,119 @@ struct XXH3_state_s {
  * Note that this doesn't prepare the state for a streaming operation,
  * it's still necessary to use XXH3_NNbits_reset*() afterwards.
  */
-#define XXH3_INITSTATE(XXH3_state_ptr)   { (XXH3_state_ptr)->seed = 0; }
+#define XXH3_INITSTATE(XXH3_state_ptr)                       \
+    do {                                                     \
+        XXH3_state_t* tmp_xxh3_state_ptr = (XXH3_state_ptr); \
+        tmp_xxh3_state_ptr->seed = 0;                        \
+        tmp_xxh3_state_ptr->extSecret = NULL;                \
+    } while(0)
 
 
-/* XXH128() :
+/*!
  * simple alias to pre-selected XXH3_128bits variant
  */
-XXH_PUBLIC_API XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed);
 
 
 /* ===   Experimental API   === */
 /* Symbols defined below must be considered tied to a specific library version. */
 
-/*
+/*!
  * XXH3_generateSecret():
  *
  * Derive a high-entropy secret from any user-defined content, named customSeed.
  * The generated secret can be used in combination with `*_withSecret()` functions.
- * The `_withSecret()` variants are useful to provide a higher level of protection than 64-bit seed,
- * as it becomes much more difficult for an external actor to guess how to impact the calculation logic.
+ * The `_withSecret()` variants are useful to provide a higher level of protection
+ * than 64-bit seed, as it becomes much more difficult for an external actor to
+ * guess how to impact the calculation logic.
  *
  * The function accepts as input a custom seed of any length and any content,
- * and derives from it a high-entropy secret of length @secretSize
- * into an already allocated buffer @secretBuffer.
- * @secretSize must be >= XXH3_SECRET_SIZE_MIN
+ * and derives from it a high-entropy secret of length @p secretSize into an
+ * already allocated buffer @p secretBuffer.
  *
  * The generated secret can then be used with any `*_withSecret()` variant.
- * Functions `XXH3_128bits_withSecret()`, `XXH3_64bits_withSecret()`,
- * `XXH3_128bits_reset_withSecret()` and `XXH3_64bits_reset_withSecret()`
+ * The functions @ref XXH3_128bits_withSecret(), @ref XXH3_64bits_withSecret(),
+ * @ref XXH3_128bits_reset_withSecret() and @ref XXH3_64bits_reset_withSecret()
  * are part of this list. They all accept a `secret` parameter
- * which must be large enough for implementation reasons (>= XXH3_SECRET_SIZE_MIN)
+ * which must be large enough for implementation reasons (>= @ref XXH3_SECRET_SIZE_MIN)
  * _and_ feature very high entropy (consist of random-looking bytes).
- * These conditions can be a high bar to meet, so
- * XXH3_generateSecret() can be employed to ensure proper quality.
+ * These conditions can be a high bar to meet, so @ref XXH3_generateSecret() can
+ * be employed to ensure proper quality.
  *
- * customSeed can be anything. It can have any size, even small ones,
- * and its content can be anything, even "poor entropy" sources such as a bunch of zeroes.
- * The resulting `secret` will nonetheless provide all required qualities.
+ * @p customSeed can be anything. It can have any size, even small ones,
+ * and its content can be anything, even "poor entropy" sources such as a bunch
+ * of zeroes. The resulting `secret` will nonetheless provide all required qualities.
  *
- * When customSeedSize > 0, supplying NULL as customSeed is undefined behavior.
+ * @pre
+ *   - @p secretSize must be >= @ref XXH3_SECRET_SIZE_MIN
+ *   - When @p customSeedSize > 0, supplying NULL as customSeed is undefined behavior.
+ *
+ * Example code:
+ * @code{.c}
+ *    #include <stdio.h>
+ *    #include <stdlib.h>
+ *    #include <string.h>
+ *    #define XXH_STATIC_LINKING_ONLY // expose unstable API
+ *    #include "xxhash.h"
+ *    // Hashes argv[2] using the entropy from argv[1].
+ *    int main(int argc, char* argv[])
+ *    {
+ *        char secret[XXH3_SECRET_SIZE_MIN];
+ *        if (argv != 3) { return 1; }
+ *        XXH3_generateSecret(secret, sizeof(secret), argv[1], strlen(argv[1]));
+ *        XXH64_hash_t h = XXH3_64bits_withSecret(
+ *             argv[2], strlen(argv[2]),
+ *             secret, sizeof(secret)
+ *        );
+ *        printf("%016llx\n", (unsigned long long) h);
+ *    }
+ * @endcode
  */
-XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize);
-
+XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize);
 
-/*
- * XXH3_generateSecret_fromSeed():
- *
- * Generate the same secret as the _withSeed() variants.
- *
- * The resulting secret has a length of XXH3_SECRET_DEFAULT_SIZE (necessarily).
- * @secretBuffer must be already allocated, of size at least XXH3_SECRET_DEFAULT_SIZE bytes.
+/*!
+ * @brief Generate the same secret as the _withSeed() variants.
  *
  * The generated secret can be used in combination with
  *`*_withSecret()` and `_withSecretandSeed()` variants.
- * This generator is notably useful in combination with `_withSecretandSeed()`,
- * as a way to emulate a faster `_withSeed()` variant.
+ *
+ * Example C++ `std::string` hash class:
+ * @code{.cpp}
+ *    #include <string>
+ *    #define XXH_STATIC_LINKING_ONLY // expose unstable API
+ *    #include "xxhash.h"
+ *    // Slow, seeds each time
+ *    class HashSlow {
+ *        XXH64_hash_t seed;
+ *    public:
+ *        HashSlow(XXH64_hash_t s) : seed{s} {}
+ *        size_t operator()(const std::string& x) const {
+ *            return size_t{XXH3_64bits_withSeed(x.c_str(), x.length(), seed)};
+ *        }
+ *    };
+ *    // Fast, caches the seeded secret for future uses.
+ *    class HashFast {
+ *        unsigned char secret[XXH3_SECRET_SIZE_MIN];
+ *    public:
+ *        HashFast(XXH64_hash_t s) {
+ *            XXH3_generateSecret_fromSeed(secret, seed);
+ *        }
+ *        size_t operator()(const std::string& x) const {
+ *            return size_t{
+ *                XXH3_64bits_withSecret(x.c_str(), x.length(), secret, sizeof(secret))
+ *            };
+ *        }
+ *    };
+ * @endcode
+ * @param secretBuffer A writable buffer of @ref XXH3_SECRET_SIZE_MIN bytes
+ * @param seed The seed to seed the state.
  */
-XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed);
+XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed);
 
-/*
- * *_withSecretandSeed() :
+/*!
  * These variants generate hash values using either
- * @seed for "short" keys (< XXH3_MIDSIZE_MAX = 240 bytes)
- * or @secret for "large" keys (>= XXH3_MIDSIZE_MAX).
+ * @seed for "short" keys (< XXH3_MIDSIZE_MAX = 240 bytes)
+ * or @secret for "large" keys (>= XXH3_MIDSIZE_MAX).
  *
  * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`.
  * `_withSeed()` has to generate the secret on the fly for "large" keys.
@@ -1181,7 +1699,7 @@ XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_
  * which requires more instructions than _withSeed() variants.
  * Therefore, _withSecretandSeed variant combines the best of both worlds.
  *
- * When @secret has been generated by XXH3_generateSecret_fromSeed(),
+ * When @secret has been generated by XXH3_generateSecret_fromSeed(),
  * this variant produces *exactly* the same results as `_withSeed()` variant,
  * hence offering only a pure speed benefit on "large" input,
  * by skipping the need to regenerate the secret for every large input.
@@ -1190,32 +1708,34 @@ XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_
  * for example with XXH3_64bits(), which then becomes the seed,
  * and then employ both the seed and the secret in _withSecretandSeed().
  * On top of speed, an added benefit is that each bit in the secret
- * has a 50% chance to swap each bit in the output,
- * via its impact to the seed.
+ * has a 50% chance to swap each bit in the output, via its impact to the seed.
+ *
  * This is not guaranteed when using the secret directly in "small data" scenarios,
  * because only portions of the secret are employed for small data.
  */
-XXH_PUBLIC_API XXH64_hash_t
-XXH3_64bits_withSecretandSeed(const void* data, size_t len,
-                              const void* secret, size_t secretSize,
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t
+XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* data, size_t len,
+                              XXH_NOESCAPE const void* secret, size_t secretSize,
                               XXH64_hash_t seed);
-
-XXH_PUBLIC_API XXH128_hash_t
-XXH3_128bits_withSecretandSeed(const void* data, size_t len,
-                               const void* secret, size_t secretSize,
+/*! @copydoc XXH3_64bits_withSecretandSeed() */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t
+XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length,
+                               XXH_NOESCAPE const void* secret, size_t secretSize,
                                XXH64_hash_t seed64);
-
+#ifndef XXH_NO_STREAM
+/*! @copydoc XXH3_64bits_withSecretandSeed() */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
-                                    const void* secret, size_t secretSize,
+XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr,
+                                    XXH_NOESCAPE const void* secret, size_t secretSize,
                                     XXH64_hash_t seed64);
-
+/*! @copydoc XXH3_64bits_withSecretandSeed() */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
-                                     const void* secret, size_t secretSize,
+XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr,
+                                     XXH_NOESCAPE const void* secret, size_t secretSize,
                                      XXH64_hash_t seed64);
+#endif /* !XXH_NO_STREAM */
 
-
+#endif  /* !XXH_NO_XXH3 */
 #endif  /* XXH_NO_LONG_LONG */
 #if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)
 #  define XXH_IMPLEMENTATION
@@ -1269,7 +1789,7 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
 /*!
  * @brief Define this to disable 64-bit code.
  *
- * Useful if only using the @ref xxh32_family and you have a strict C90 compiler.
+ * Useful if only using the @ref XXH32_family and you have a strict C90 compiler.
  */
 #  define XXH_NO_LONG_LONG
 #  undef XXH_NO_LONG_LONG /* don't actually */
@@ -1292,7 +1812,7 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
  *     Use `memcpy()`. Safe and portable. Note that most modern compilers will
  *     eliminate the function call and treat it as an unaligned access.
  *
- *  - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((packed))`
+ *  - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((aligned(1)))`
  *   @par
  *     Depends on compiler extensions and is therefore not portable.
  *     This method is safe _if_ your compiler supports it,
@@ -1312,20 +1832,48 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
  *     inline small `memcpy()` calls, and it might also be faster on big-endian
  *     systems which lack a native byteswap instruction. However, some compilers
  *     will emit literal byteshifts even if the target supports unaligned access.
- *  .
+ *
  *
  * @warning
  *   Methods 1 and 2 rely on implementation-defined behavior. Use these with
  *   care, as what works on one compiler/platform/optimization level may cause
  *   another to read garbage data or even crash.
  *
- * See http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details.
+ * See https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details.
  *
  * Prefer these methods in priority order (0 > 3 > 1 > 2)
  */
 #  define XXH_FORCE_MEMORY_ACCESS 0
 
 /*!
+ * @def XXH_SIZE_OPT
+ * @brief Controls how much xxHash optimizes for size.
+ *
+ * xxHash, when compiled, tends to result in a rather large binary size. This
+ * is mostly due to heavy usage to forced inlining and constant folding of the
+ * @ref XXH3_family to increase performance.
+ *
+ * However, some developers prefer size over speed. This option can
+ * significantly reduce the size of the generated code. When using the `-Os`
+ * or `-Oz` options on GCC or Clang, this is defined to 1 by default,
+ * otherwise it is defined to 0.
+ *
+ * Most of these size optimizations can be controlled manually.
+ *
+ * This is a number from 0-2.
+ *  - `XXH_SIZE_OPT` == 0: Default. xxHash makes no size optimizations. Speed
+ *    comes first.
+ *  - `XXH_SIZE_OPT` == 1: Default for `-Os` and `-Oz`. xxHash is more
+ *    conservative and disables hacks that increase code size. It implies the
+ *    options @ref XXH_NO_INLINE_HINTS == 1, @ref XXH_FORCE_ALIGN_CHECK == 0,
+ *    and @ref XXH3_NEON_LANES == 8 if they are not already defined.
+ *  - `XXH_SIZE_OPT` == 2: xxHash tries to make itself as small as possible.
+ *    Performance may cry. For example, the single shot functions just use the
+ *    streaming API.
+ */
+#  define XXH_SIZE_OPT 0
+
+/*!
  * @def XXH_FORCE_ALIGN_CHECK
  * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32()
  * and XXH64() only).
@@ -1346,9 +1894,11 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
  *
  * In these cases, the alignment check can be removed by setting this macro to 0.
  * Then the code will always use unaligned memory access.
- * Align check is automatically disabled on x86, x64 & arm64,
+ * Align check is automatically disabled on x86, x64, ARM64, and some ARM chips
  * which are platforms known to offer good unaligned memory accesses performance.
  *
+ * It is also disabled by default when @ref XXH_SIZE_OPT >= 1.
+ *
  * This option does not affect XXH3 (only XXH32 and XXH64).
  */
 #  define XXH_FORCE_ALIGN_CHECK 0
@@ -1370,12 +1920,29 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
  * XXH_NO_INLINE_HINTS marks all internal functions as static, giving the
  * compiler full control on whether to inline or not.
  *
- * When not optimizing (-O0), optimizing for size (-Os, -Oz), or using
- * -fno-inline with GCC or Clang, this will automatically be defined.
+ * When not optimizing (-O0), using `-fno-inline` with GCC or Clang, or if
+ * @ref XXH_SIZE_OPT >= 1, this will automatically be defined.
  */
 #  define XXH_NO_INLINE_HINTS 0
 
 /*!
+ * @def XXH3_INLINE_SECRET
+ * @brief Determines whether to inline the XXH3 withSecret code.
+ *
+ * When the secret size is known, the compiler can improve the performance
+ * of XXH3_64bits_withSecret() and XXH3_128bits_withSecret().
+ *
+ * However, if the secret size is not known, it doesn't have any benefit. This
+ * happens when xxHash is compiled into a global symbol. Therefore, if
+ * @ref XXH_INLINE_ALL is *not* defined, this will be defined to 0.
+ *
+ * Additionally, this defaults to 0 on GCC 12+, which has an issue with function pointers
+ * that are *sometimes* force inline on -Og, and it is impossible to automatically
+ * detect this optimization level.
+ */
+#  define XXH3_INLINE_SECRET 0
+
+/*!
  * @def XXH32_ENDJMP
  * @brief Whether to use a jump for `XXH32_finalize`.
  *
@@ -1396,34 +1963,45 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
  */
 #  define XXH_OLD_NAMES
 #  undef XXH_OLD_NAMES /* don't actually use, it is ugly. */
+
+/*!
+ * @def XXH_NO_STREAM
+ * @brief Disables the streaming API.
+ *
+ * When xxHash is not inlined and the streaming functions are not used, disabling
+ * the streaming functions can improve code size significantly, especially with
+ * the @ref XXH3_family which tends to make constant folded copies of itself.
+ */
+#  define XXH_NO_STREAM
+#  undef XXH_NO_STREAM /* don't actually */
 #endif /* XXH_DOXYGEN */
 /*!
  * @}
  */
 
 #ifndef XXH_FORCE_MEMORY_ACCESS   /* can be defined externally, on command line for example */
-   /* prefer __packed__ structures (method 1) for gcc on armv7+ and mips */
-#  if !defined(__clang__) && \
-( \
-    (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \
-    ( \
-        defined(__GNUC__) && ( \
-            (defined(__ARM_ARCH) && __ARM_ARCH >= 7) || \
-            ( \
-                defined(__mips__) && \
-                (__mips <= 5 || __mips_isa_rev < 6) && \
-                (!defined(__mips16) || defined(__mips_mips16e2)) \
-            ) \
-        ) \
-    ) \
-)
+   /* prefer __packed__ structures (method 1) for GCC
+    * < ARMv7 with unaligned access (e.g. Raspbian armhf) still uses byte shifting, so we use memcpy
+    * which for some reason does unaligned loads. */
+#  if defined(__GNUC__) && !(defined(__ARM_ARCH) && __ARM_ARCH < 7 && defined(__ARM_FEATURE_UNALIGNED))
 #    define XXH_FORCE_MEMORY_ACCESS 1
 #  endif
 #endif
 
+#ifndef XXH_SIZE_OPT
+   /* default to 1 for -Os or -Oz */
+#  if (defined(__GNUC__) || defined(__clang__)) && defined(__OPTIMIZE_SIZE__)
+#    define XXH_SIZE_OPT 1
+#  else
+#    define XXH_SIZE_OPT 0
+#  endif
+#endif
+
 #ifndef XXH_FORCE_ALIGN_CHECK  /* can be defined externally */
-#  if defined(__i386)  || defined(__x86_64__) || defined(__aarch64__) \
-   || defined(_M_IX86) || defined(_M_X64)     || defined(_M_ARM64) /* visual */
+   /* don't check on sizeopt, x86, aarch64, or arm when unaligned access is available */
+#  if XXH_SIZE_OPT >= 1 || \
+      defined(__i386)  || defined(__x86_64__) || defined(__aarch64__) || defined(__ARM_FEATURE_UNALIGNED) \
+   || defined(_M_IX86) || defined(_M_X64)     || defined(_M_ARM64)    || defined(_M_ARM) /* visual */
 #    define XXH_FORCE_ALIGN_CHECK 0
 #  else
 #    define XXH_FORCE_ALIGN_CHECK 1
@@ -1431,14 +2009,22 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
 #endif
 
 #ifndef XXH_NO_INLINE_HINTS
-#  if defined(__OPTIMIZE_SIZE__) /* -Os, -Oz */ \
-   || defined(__NO_INLINE__)     /* -O0, -fno-inline */
+#  if XXH_SIZE_OPT >= 1 || defined(__NO_INLINE__)  /* -O0, -fno-inline */
 #    define XXH_NO_INLINE_HINTS 1
 #  else
 #    define XXH_NO_INLINE_HINTS 0
 #  endif
 #endif
 
+#ifndef XXH3_INLINE_SECRET
+#  if (defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12) \
+     || !defined(XXH_INLINE_ALL)
+#    define XXH3_INLINE_SECRET 0
+#  else
+#    define XXH3_INLINE_SECRET 1
+#  endif
+#endif
+
 #ifndef XXH32_ENDJMP
 /* generally preferable for performance */
 #  define XXH32_ENDJMP 0
@@ -1453,6 +2039,24 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
 /* *************************************
 *  Includes & Memory related functions
 ***************************************/
+#if defined(XXH_NO_STREAM)
+/* nothing */
+#elif defined(XXH_NO_STDLIB)
+
+/* When requesting to disable any mention of stdlib,
+ * the library loses the ability to invoked malloc / free.
+ * In practice, it means that functions like `XXH*_createState()`
+ * will always fail, and return NULL.
+ * This flag is useful in situations where
+ * xxhash.h is integrated into some kernel, embedded or limited environment
+ * without access to dynamic allocation.
+ */
+
+static XXH_CONSTF void* XXH_malloc(size_t s) { (void)s; return NULL; }
+static void XXH_free(void* p) { (void)p; }
+
+#else
+
 /*
  * Modify the local functions below should you wish to use
  * different memory routines for malloc() and free()
@@ -1463,7 +2067,7 @@ XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
  * @internal
  * @brief Modify this function to use a different routine than malloc().
  */
-static void* XXH_malloc(size_t s) { return malloc(s); }
+static XXH_MALLOCF void* XXH_malloc(size_t s) { return malloc(s); }
 
 /*!
  * @internal
@@ -1471,6 +2075,8 @@ static void* XXH_malloc(size_t s) { return malloc(s); }
  */
 static void XXH_free(void* p) { free(p); }
 
+#endif  /* XXH_NO_STDLIB */
+
 #include <string.h>
 
 /*!
@@ -1515,6 +2121,11 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size)
 #  define XXH_NO_INLINE static
 #endif
 
+#if XXH3_INLINE_SECRET
+#  define XXH3_WITH_SECRET_INLINE XXH_FORCE_INLINE
+#else
+#  define XXH3_WITH_SECRET_INLINE XXH_NO_INLINE
+#endif
 
 
 /* *************************************
@@ -1540,14 +2151,17 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size)
 #  include <assert.h>   /* note: can still be disabled with NDEBUG */
 #  define XXH_ASSERT(c)   assert(c)
 #else
-#  define XXH_ASSERT(c)   ((void)0)
+#  if defined(__INTEL_COMPILER)
+#    define XXH_ASSERT(c)   XXH_ASSUME((unsigned char) (c))
+#  else
+#    define XXH_ASSERT(c)   XXH_ASSUME(c)
+#  endif
 #endif
 
 /* note: use after variable declarations */
 #ifndef XXH_STATIC_ASSERT
 #  if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)    /* C11 */
-#    include <assert.h>
-#    define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0)
+#    define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { _Static_assert((c),m); } while(0)
 #  elif defined(__cplusplus) && (__cplusplus >= 201103L)            /* C++11 */
 #    define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0)
 #  else
@@ -1573,11 +2187,19 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size)
  * XXH3_initCustomSecret_scalar().
  */
 #if defined(__GNUC__) || defined(__clang__)
-#  define XXH_COMPILER_GUARD(var) __asm__ __volatile__("" : "+r" (var))
+#  define XXH_COMPILER_GUARD(var) __asm__("" : "+r" (var))
 #else
 #  define XXH_COMPILER_GUARD(var) ((void)0)
 #endif
 
+/* Specifically for NEON vectors which use the "w" constraint, on
+ * Clang. */
+#if defined(__clang__) && defined(__ARM_ARCH) && !defined(__wasm__)
+#  define XXH_COMPILER_GUARD_CLANG_NEON(var) __asm__("" : "+w" (var))
+#else
+#  define XXH_COMPILER_GUARD_CLANG_NEON(var) ((void)0)
+#endif
+
 /* *************************************
 *  Basic Types
 ***************************************/
@@ -1592,6 +2214,7 @@ static void* XXH_memcpy(void* dest, const void* src, size_t size)
 typedef XXH32_hash_t xxh_u32;
 
 #ifdef XXH_OLD_NAMES
+#  warning "XXH_OLD_NAMES is planned to be removed starting v0.9. If the program depends on it, consider moving away from it by employing newer type names directly"
 #  define BYTE xxh_u8
 #  define U8   xxh_u8
 #  define U32  xxh_u32
@@ -1665,25 +2288,26 @@ static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr;
 #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
 
 /*
- * __pack instructions are safer but compiler specific, hence potentially
- * problematic for some compilers.
- *
- * Currently only defined for GCC and ICC.
+ * __attribute__((aligned(1))) is supported by gcc and clang. Originally the
+ * documentation claimed that it only increased the alignment, but actually it
+ * can decrease it on gcc, clang, and icc:
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502,
+ * https://gcc.godbolt.org/z/xYez1j67Y.
  */
 #ifdef XXH_OLD_NAMES
 typedef union { xxh_u32 u32; } __attribute__((packed)) unalign;
 #endif
 static xxh_u32 XXH_read32(const void* ptr)
 {
-    typedef union { xxh_u32 u32; } __attribute__((packed)) xxh_unalign;
-    return ((const xxh_unalign*)ptr)->u32;
+    typedef __attribute__((aligned(1))) xxh_u32 xxh_unalign32;
+    return *((const xxh_unalign32*)ptr);
 }
 
 #else
 
 /*
  * Portable and safe solution. Generally efficient.
- * see: http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
+ * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
  */
 static xxh_u32 XXH_read32(const void* memPtr)
 {
@@ -1759,6 +2383,51 @@ static int XXH_isLittleEndian(void)
 #  define XXH_HAS_BUILTIN(x) 0
 #endif
 
+
+
+/*
+ * C23 and future versions have standard "unreachable()".
+ * Once it has been implemented reliably we can add it as an
+ * additional case:
+ *
+ * ```
+ * #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= XXH_C23_VN)
+ * #  include <stddef.h>
+ * #  ifdef unreachable
+ * #    define XXH_UNREACHABLE() unreachable()
+ * #  endif
+ * #endif
+ * ```
+ *
+ * Note C++23 also has std::unreachable() which can be detected
+ * as follows:
+ * ```
+ * #if defined(__cpp_lib_unreachable) && (__cpp_lib_unreachable >= 202202L)
+ * #  include <utility>
+ * #  define XXH_UNREACHABLE() std::unreachable()
+ * #endif
+ * ```
+ * NB: `__cpp_lib_unreachable` is defined in the `<version>` header.
+ * We don't use that as including `<utility>` in `extern "C"` blocks
+ * doesn't work on GCC12
+ */
+
+#if XXH_HAS_BUILTIN(__builtin_unreachable)
+#  define XXH_UNREACHABLE() __builtin_unreachable()
+
+#elif defined(_MSC_VER)
+#  define XXH_UNREACHABLE() __assume(0)
+
+#else
+#  define XXH_UNREACHABLE()
+#endif
+
+#if XXH_HAS_BUILTIN(__builtin_assume)
+#  define XXH_ASSUME(c) __builtin_assume(c)
+#else
+#  define XXH_ASSUME(c) if (!(c)) { XXH_UNREACHABLE(); }
+#endif
+
 /*!
  * @internal
  * @def XXH_rotl32(x,r)
@@ -1881,8 +2550,10 @@ XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; }
 *********************************************************************/
 /*!
  * @}
- * @defgroup xxh32_impl XXH32 implementation
+ * @defgroup XXH32_impl XXH32 implementation
  * @ingroup impl
+ *
+ * Details on the XXH32 implementation.
  * @{
  */
  /* #define instead of static const, to be used as initializers */
@@ -1916,7 +2587,7 @@ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
     acc += input * XXH_PRIME32_2;
     acc  = XXH_rotl32(acc, 13);
     acc *= XXH_PRIME32_1;
-#if (defined(__SSE4_1__) || defined(__aarch64__)) && !defined(XXH_ENABLE_AUTOVECTORIZE)
+#if (defined(__SSE4_1__) || defined(__aarch64__) || defined(__wasm_simd128__)) && !defined(XXH_ENABLE_AUTOVECTORIZE)
     /*
      * UGLY HACK:
      * A compiler fence is the only thing that prevents GCC and Clang from
@@ -1946,9 +2617,12 @@ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
      *   can load data, while v3 can multiply. SSE forces them to operate
      *   together.
      *
-     * This is also enabled on AArch64, as Clang autovectorizes it incorrectly
-     * and it is pointless writing a NEON implementation that is basically the
-     * same speed as scalar for XXH32.
+     * This is also enabled on AArch64, as Clang is *very aggressive* in vectorizing
+     * the loop. NEON is only faster on the A53, and with the newer cores, it is less
+     * than half the speed.
+     *
+     * Additionally, this is used on WASM SIMD128 because it JITs to the same
+     * SIMD instructions and has the same issue.
      */
     XXH_COMPILER_GUARD(acc);
 #endif
@@ -1962,17 +2636,17 @@ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
  * The final mix ensures that all input bits have a chance to impact any bit in
  * the output digest, resulting in an unbiased distribution.
  *
- * @param h32 The hash to avalanche.
+ * @param hash The hash to avalanche.
  * @return The avalanched hash.
  */
-static xxh_u32 XXH32_avalanche(xxh_u32 h32)
+static xxh_u32 XXH32_avalanche(xxh_u32 hash)
 {
-    h32 ^= h32 >> 15;
-    h32 *= XXH_PRIME32_2;
-    h32 ^= h32 >> 13;
-    h32 *= XXH_PRIME32_3;
-    h32 ^= h32 >> 16;
-    return(h32);
+    hash ^= hash >> 15;
+    hash *= XXH_PRIME32_2;
+    hash ^= hash >> 13;
+    hash *= XXH_PRIME32_3;
+    hash ^= hash >> 16;
+    return hash;
 }
 
 #define XXH_get32bits(p) XXH_readLE32_align(p, align)
@@ -1985,24 +2659,25 @@ static xxh_u32 XXH32_avalanche(xxh_u32 h32)
  * This final stage will digest them to ensure that all input bytes are present
  * in the final mix.
  *
- * @param h32 The hash to finalize.
+ * @param hash The hash to finalize.
  * @param ptr The pointer to the remaining input.
  * @param len The remaining length, modulo 16.
  * @param align Whether @p ptr is aligned.
  * @return The finalized hash.
+ * @see XXH64_finalize().
  */
-static xxh_u32
-XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align)
+static XXH_PUREF xxh_u32
+XXH32_finalize(xxh_u32 hash, const xxh_u8* ptr, size_t len, XXH_alignment align)
 {
-#define XXH_PROCESS1 do {                           \
-    h32 += (*ptr++) * XXH_PRIME32_5;                \
-    h32 = XXH_rotl32(h32, 11) * XXH_PRIME32_1;      \
+#define XXH_PROCESS1 do {                             \
+    hash += (*ptr++) * XXH_PRIME32_5;                 \
+    hash = XXH_rotl32(hash, 11) * XXH_PRIME32_1;      \
 } while (0)
 
-#define XXH_PROCESS4 do {                           \
-    h32 += XXH_get32bits(ptr) * XXH_PRIME32_3;      \
-    ptr += 4;                                   \
-    h32  = XXH_rotl32(h32, 17) * XXH_PRIME32_4;     \
+#define XXH_PROCESS4 do {                             \
+    hash += XXH_get32bits(ptr) * XXH_PRIME32_3;       \
+    ptr += 4;                                         \
+    hash  = XXH_rotl32(hash, 17) * XXH_PRIME32_4;     \
 } while (0)
 
     if (ptr==NULL) XXH_ASSERT(len == 0);
@@ -2018,49 +2693,49 @@ XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align)
             XXH_PROCESS1;
             --len;
         }
-        return XXH32_avalanche(h32);
+        return XXH32_avalanche(hash);
     } else {
          switch(len&15) /* or switch(bEnd - p) */ {
            case 12:      XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 8:       XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 4:       XXH_PROCESS4;
-                         return XXH32_avalanche(h32);
+                         return XXH32_avalanche(hash);
 
            case 13:      XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 9:       XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 5:       XXH_PROCESS4;
                          XXH_PROCESS1;
-                         return XXH32_avalanche(h32);
+                         return XXH32_avalanche(hash);
 
            case 14:      XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 10:      XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 6:       XXH_PROCESS4;
                          XXH_PROCESS1;
                          XXH_PROCESS1;
-                         return XXH32_avalanche(h32);
+                         return XXH32_avalanche(hash);
 
            case 15:      XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 11:      XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 7:       XXH_PROCESS4;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 3:       XXH_PROCESS1;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 2:       XXH_PROCESS1;
-                         XXH_FALLTHROUGH;
+                         XXH_FALLTHROUGH;  /* fallthrough */
            case 1:       XXH_PROCESS1;
-                         XXH_FALLTHROUGH;
-           case 0:       return XXH32_avalanche(h32);
+                         XXH_FALLTHROUGH;  /* fallthrough */
+           case 0:       return XXH32_avalanche(hash);
         }
         XXH_ASSERT(0);
-        return h32;   /* reaching this point is deemed impossible */
+        return hash;   /* reaching this point is deemed impossible */
     }
 }
 
@@ -2080,7 +2755,7 @@ XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align)
  * @param align Whether @p input is aligned.
  * @return The calculated hash.
  */
-XXH_FORCE_INLINE xxh_u32
+XXH_FORCE_INLINE XXH_PUREF xxh_u32
 XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align)
 {
     xxh_u32 h32;
@@ -2113,10 +2788,10 @@ XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment
     return XXH32_finalize(h32, input, len&15, align);
 }
 
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed)
 {
-#if 0
+#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2
     /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
     XXH32_state_t state;
     XXH32_reset(&state, seed);
@@ -2135,42 +2810,39 @@ XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t s
 
 
 /*******   Hash streaming   *******/
-/*!
- * @ingroup xxh32_family
- */
+#ifndef XXH_NO_STREAM
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)
 {
     return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));
 }
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)
 {
     XXH_free(statePtr);
     return XXH_OK;
 }
 
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)
 {
     XXH_memcpy(dstState, srcState, sizeof(*dstState));
 }
 
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed)
 {
-    XXH32_state_t state;   /* using a local state to memcpy() in order to avoid strict-aliasing warnings */
-    memset(&state, 0, sizeof(state));
-    state.v[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
-    state.v[1] = seed + XXH_PRIME32_2;
-    state.v[2] = seed + 0;
-    state.v[3] = seed - XXH_PRIME32_1;
-    /* do not write into reserved, planned to be removed in a future version */
-    XXH_memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved));
+    XXH_ASSERT(statePtr != NULL);
+    memset(statePtr, 0, sizeof(*statePtr));
+    statePtr->v[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
+    statePtr->v[1] = seed + XXH_PRIME32_2;
+    statePtr->v[2] = seed + 0;
+    statePtr->v[3] = seed - XXH_PRIME32_1;
     return XXH_OK;
 }
 
 
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH_errorcode
 XXH32_update(XXH32_state_t* state, const void* input, size_t len)
 {
@@ -2225,7 +2897,7 @@ XXH32_update(XXH32_state_t* state, const void* input, size_t len)
 }
 
 
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state)
 {
     xxh_u32 h32;
@@ -2243,12 +2915,12 @@ XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state)
 
     return XXH32_finalize(h32, (const xxh_u8*)state->mem32, state->memsize, XXH_aligned);
 }
-
+#endif /* !XXH_NO_STREAM */
 
 /*******   Canonical representation   *******/
 
 /*!
- * @ingroup xxh32_family
+ * @ingroup XXH32_family
  * The default return values from XXH functions are unsigned 32 and 64 bit
  * integers.
  *
@@ -2267,7 +2939,7 @@ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t
     if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);
     XXH_memcpy(dst, &hash, sizeof(*dst));
 }
-/*! @ingroup xxh32_family */
+/*! @ingroup XXH32_family */
 XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)
 {
     return XXH_readBE32(src);
@@ -2308,25 +2980,26 @@ static xxh_u64 XXH_read64(const void* memPtr)
 #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
 
 /*
- * __pack instructions are safer, but compiler specific, hence potentially
- * problematic for some compilers.
- *
- * Currently only defined for GCC and ICC.
+ * __attribute__((aligned(1))) is supported by gcc and clang. Originally the
+ * documentation claimed that it only increased the alignment, but actually it
+ * can decrease it on gcc, clang, and icc:
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502,
+ * https://gcc.godbolt.org/z/xYez1j67Y.
  */
 #ifdef XXH_OLD_NAMES
 typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) unalign64;
 #endif
 static xxh_u64 XXH_read64(const void* ptr)
 {
-    typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) xxh_unalign64;
-    return ((const xxh_unalign64*)ptr)->u64;
+    typedef __attribute__((aligned(1))) xxh_u64 xxh_unalign64;
+    return *((const xxh_unalign64*)ptr);
 }
 
 #else
 
 /*
  * Portable and safe solution. Generally efficient.
- * see: http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
+ * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
  */
 static xxh_u64 XXH_read64(const void* memPtr)
 {
@@ -2410,8 +3083,10 @@ XXH_readLE64_align(const void* ptr, XXH_alignment align)
 /*******   xxh64   *******/
 /*!
  * @}
- * @defgroup xxh64_impl XXH64 implementation
+ * @defgroup XXH64_impl XXH64 implementation
  * @ingroup impl
+ *
+ * Details on the XXH64 implementation.
  * @{
  */
 /* #define rather that static const, to be used as initializers */
@@ -2429,6 +3104,7 @@ XXH_readLE64_align(const void* ptr, XXH_alignment align)
 #  define PRIME64_5 XXH_PRIME64_5
 #endif
 
+/*! @copydoc XXH32_round */
 static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input)
 {
     acc += input * XXH_PRIME64_2;
@@ -2445,43 +3121,59 @@ static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val)
     return acc;
 }
 
-static xxh_u64 XXH64_avalanche(xxh_u64 h64)
+/*! @copydoc XXH32_avalanche */
+static xxh_u64 XXH64_avalanche(xxh_u64 hash)
 {
-    h64 ^= h64 >> 33;
-    h64 *= XXH_PRIME64_2;
-    h64 ^= h64 >> 29;
-    h64 *= XXH_PRIME64_3;
-    h64 ^= h64 >> 32;
-    return h64;
+    hash ^= hash >> 33;
+    hash *= XXH_PRIME64_2;
+    hash ^= hash >> 29;
+    hash *= XXH_PRIME64_3;
+    hash ^= hash >> 32;
+    return hash;
 }
 
 
 #define XXH_get64bits(p) XXH_readLE64_align(p, align)
 
-static xxh_u64
-XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align)
+/*!
+ * @internal
+ * @brief Processes the last 0-31 bytes of @p ptr.
+ *
+ * There may be up to 31 bytes remaining to consume from the input.
+ * This final stage will digest them to ensure that all input bytes are present
+ * in the final mix.
+ *
+ * @param hash The hash to finalize.
+ * @param ptr The pointer to the remaining input.
+ * @param len The remaining length, modulo 32.
+ * @param align Whether @p ptr is aligned.
+ * @return The finalized hash
+ * @see XXH32_finalize().
+ */
+static XXH_PUREF xxh_u64
+XXH64_finalize(xxh_u64 hash, const xxh_u8* ptr, size_t len, XXH_alignment align)
 {
     if (ptr==NULL) XXH_ASSERT(len == 0);
     len &= 31;
     while (len >= 8) {
         xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr));
         ptr += 8;
-        h64 ^= k1;
-        h64  = XXH_rotl64(h64,27) * XXH_PRIME64_1 + XXH_PRIME64_4;
+        hash ^= k1;
+        hash  = XXH_rotl64(hash,27) * XXH_PRIME64_1 + XXH_PRIME64_4;
         len -= 8;
     }
     if (len >= 4) {
-        h64 ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1;
+        hash ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1;
         ptr += 4;
-        h64 = XXH_rotl64(h64, 23) * XXH_PRIME64_2 + XXH_PRIME64_3;
+        hash = XXH_rotl64(hash, 23) * XXH_PRIME64_2 + XXH_PRIME64_3;
         len -= 4;
     }
     while (len > 0) {
-        h64 ^= (*ptr++) * XXH_PRIME64_5;
-        h64 = XXH_rotl64(h64, 11) * XXH_PRIME64_1;
+        hash ^= (*ptr++) * XXH_PRIME64_5;
+        hash = XXH_rotl64(hash, 11) * XXH_PRIME64_1;
         --len;
     }
-    return  XXH64_avalanche(h64);
+    return  XXH64_avalanche(hash);
 }
 
 #ifdef XXH_OLD_NAMES
@@ -2494,7 +3186,15 @@ XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align)
 #  undef XXH_PROCESS8_64
 #endif
 
-XXH_FORCE_INLINE xxh_u64
+/*!
+ * @internal
+ * @brief The implementation for @ref XXH64().
+ *
+ * @param input , len , seed Directly passed from @ref XXH64().
+ * @param align Whether @p input is aligned.
+ * @return The calculated hash.
+ */
+XXH_FORCE_INLINE XXH_PUREF xxh_u64
 XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align)
 {
     xxh_u64 h64;
@@ -2531,10 +3231,10 @@ XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment
 }
 
 
-/*! @ingroup xxh64_family */
-XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t seed)
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64 (XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
 {
-#if 0
+#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2
     /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
     XXH64_state_t state;
     XXH64_reset(&state, seed);
@@ -2552,42 +3252,40 @@ XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t s
 }
 
 /*******   Hash Streaming   *******/
-
-/*! @ingroup xxh64_family*/
+#ifndef XXH_NO_STREAM
+/*! @ingroup XXH64_family*/
 XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)
 {
     return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));
 }
-/*! @ingroup xxh64_family */
+/*! @ingroup XXH64_family */
 XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)
 {
     XXH_free(statePtr);
     return XXH_OK;
 }
 
-/*! @ingroup xxh64_family */
-XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState)
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dstState, const XXH64_state_t* srcState)
 {
     XXH_memcpy(dstState, srcState, sizeof(*dstState));
 }
 
-/*! @ingroup xxh64_family */
-XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, XXH64_hash_t seed)
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed)
 {
-    XXH64_state_t state;   /* use a local state to memcpy() in order to avoid strict-aliasing warnings */
-    memset(&state, 0, sizeof(state));
-    state.v[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
-    state.v[1] = seed + XXH_PRIME64_2;
-    state.v[2] = seed + 0;
-    state.v[3] = seed - XXH_PRIME64_1;
-     /* do not write into reserved64, might be removed in a future version */
-    XXH_memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved64));
+    XXH_ASSERT(statePtr != NULL);
+    memset(statePtr, 0, sizeof(*statePtr));
+    statePtr->v[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
+    statePtr->v[1] = seed + XXH_PRIME64_2;
+    statePtr->v[2] = seed + 0;
+    statePtr->v[3] = seed - XXH_PRIME64_1;
     return XXH_OK;
 }
 
-/*! @ingroup xxh64_family */
+/*! @ingroup XXH64_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH64_update (XXH64_state_t* state, const void* input, size_t len)
+XXH64_update (XXH_NOESCAPE XXH64_state_t* state, XXH_NOESCAPE const void* input, size_t len)
 {
     if (input==NULL) {
         XXH_ASSERT(len == 0);
@@ -2637,8 +3335,8 @@ XXH64_update (XXH64_state_t* state, const void* input, size_t len)
 }
 
 
-/*! @ingroup xxh64_family */
-XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state)
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_digest(XXH_NOESCAPE const XXH64_state_t* state)
 {
     xxh_u64 h64;
 
@@ -2656,20 +3354,20 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state)
 
     return XXH64_finalize(h64, (const xxh_u8*)state->mem64, (size_t)state->total_len, XXH_aligned);
 }
-
+#endif /* !XXH_NO_STREAM */
 
 /******* Canonical representation   *******/
 
-/*! @ingroup xxh64_family */
-XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash)
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash)
 {
     XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));
     if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);
     XXH_memcpy(dst, &hash, sizeof(*dst));
 }
 
-/*! @ingroup xxh64_family */
-XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src)
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src)
 {
     return XXH_readBE64(src);
 }
@@ -2682,7 +3380,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src
 ************************************************************************ */
 /*!
  * @}
- * @defgroup xxh3_impl XXH3 implementation
+ * @defgroup XXH3_impl XXH3 implementation
  * @ingroup impl
  * @{
  */
@@ -2690,11 +3388,19 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src
 /* ===   Compiler specifics   === */
 
 #if ((defined(sun) || defined(__sun)) && __cplusplus) /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */
-#  define XXH_RESTRICT /* disable */
+#  define XXH_RESTRICT   /* disable */
 #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L   /* >= C99 */
 #  define XXH_RESTRICT   restrict
+#elif (defined (__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) \
+   || (defined (__clang__)) \
+   || (defined (_MSC_VER) && (_MSC_VER >= 1400)) \
+   || (defined (__INTEL_COMPILER) && (__INTEL_COMPILER >= 1300))
+/*
+ * There are a LOT more compilers that recognize __restrict but this
+ * covers the major ones.
+ */
+#  define XXH_RESTRICT   __restrict
 #else
-/* Note: it might be useful to define __restrict or __restrict__ for some C++ compilers */
 #  define XXH_RESTRICT   /* disable */
 #endif
 
@@ -2708,17 +3414,33 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src
 #    define XXH_unlikely(x) (x)
 #endif
 
-#if defined(__GNUC__)
-#  if defined(__AVX2__)
-#    include <immintrin.h>
-#  elif defined(__SSE2__)
-#    include <emmintrin.h>
-#  elif defined(__ARM_NEON__) || defined(__ARM_NEON)
+#ifndef XXH_HAS_INCLUDE
+#  ifdef __has_include
+#    define XXH_HAS_INCLUDE(x) __has_include(x)
+#  else
+#    define XXH_HAS_INCLUDE(x) 0
+#  endif
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#  if defined(__ARM_FEATURE_SVE)
+#    include <arm_sve.h>
+#  endif
+#  if defined(__ARM_NEON__) || defined(__ARM_NEON) \
+   || (defined(_M_ARM) && _M_ARM >= 7) \
+   || defined(_M_ARM64) || defined(_M_ARM64EC) \
+   || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE(<arm_neon.h>)) /* WASM SIMD128 via SIMDe */
 #    define inline __inline__  /* circumvent a clang bug */
 #    include <arm_neon.h>
 #    undef inline
+#  elif defined(__AVX2__)
+#    include <immintrin.h>
+#  elif defined(__SSE2__)
+#    include <emmintrin.h>
 #  endif
-#elif defined(_MSC_VER)
+#endif
+
+#if defined(_MSC_VER)
 #  include <intrin.h>
 #endif
 
@@ -2818,7 +3540,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src
  * Note that these are actually implemented as macros.
  *
  * If this is not defined, it is detected automatically.
- * @ref XXH_X86DISPATCH overrides this.
+ * internal macro XXH_X86DISPATCH overrides this.
  */
 enum XXH_VECTOR_TYPE /* fake enum */ {
     XXH_SCALAR = 0,  /*!< Portable scalar version */
@@ -2830,14 +3552,19 @@ enum XXH_VECTOR_TYPE /* fake enum */ {
                       */
     XXH_AVX2   = 2,  /*!< AVX2 for Haswell and Bulldozer */
     XXH_AVX512 = 3,  /*!< AVX512 for Skylake and Icelake */
-    XXH_NEON   = 4,  /*!< NEON for most ARMv7-A and all AArch64 */
+    XXH_NEON   = 4,  /*!<
+                       * NEON for most ARMv7-A, all AArch64, and WASM SIMD128
+                       * via the SIMDeverywhere polyfill provided with the
+                       * Emscripten SDK.
+                       */
     XXH_VSX    = 5,  /*!< VSX and ZVector for POWER8/z13 (64-bit) */
+    XXH_SVE    = 6,  /*!< SVE for some ARMv8-A and ARMv9-A */
 };
 /*!
  * @ingroup tuning
  * @brief Selects the minimum alignment for XXH3's accumulators.
  *
- * When using SIMD, this should match the alignment reqired for said vector
+ * When using SIMD, this should match the alignment required for said vector
  * type, so, for example, 32 for AVX2.
  *
  * Default: Auto detected.
@@ -2853,23 +3580,27 @@ enum XXH_VECTOR_TYPE /* fake enum */ {
 #  define XXH_AVX512 3
 #  define XXH_NEON   4
 #  define XXH_VSX    5
+#  define XXH_SVE    6
 #endif
 
 #ifndef XXH_VECTOR    /* can be defined on command line */
-#  if defined(__AVX512F__)
-#    define XXH_VECTOR XXH_AVX512
-#  elif defined(__AVX2__)
-#    define XXH_VECTOR XXH_AVX2
-#  elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2))
-#    define XXH_VECTOR XXH_SSE2
+#  if defined(__ARM_FEATURE_SVE)
+#    define XXH_VECTOR XXH_SVE
 #  elif ( \
         defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \
-     || defined(_M_ARM64) || defined(_M_ARM_ARMV7VE) /* msvc */ \
+     || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) /* msvc */ \
+     || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE(<arm_neon.h>)) /* wasm simd128 via SIMDe */ \
    ) && ( \
         defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \
     || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \
    )
 #    define XXH_VECTOR XXH_NEON
+#  elif defined(__AVX512F__)
+#    define XXH_VECTOR XXH_AVX512
+#  elif defined(__AVX2__)
+#    define XXH_VECTOR XXH_AVX2
+#  elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2))
+#    define XXH_VECTOR XXH_SSE2
 #  elif (defined(__PPC64__) && defined(__POWER8_VECTOR__)) \
      || (defined(__s390x__) && defined(__VEC__)) \
      && defined(__GNUC__) /* TODO: IBM XL */
@@ -2879,6 +3610,17 @@ enum XXH_VECTOR_TYPE /* fake enum */ {
 #  endif
 #endif
 
+/* __ARM_FEATURE_SVE is only supported by GCC & Clang. */
+#if (XXH_VECTOR == XXH_SVE) && !defined(__ARM_FEATURE_SVE)
+#  ifdef _MSC_VER
+#    pragma warning(once : 4606)
+#  else
+#    warning "__ARM_FEATURE_SVE isn't supported. Use SCALAR instead."
+#  endif
+#  undef XXH_VECTOR
+#  define XXH_VECTOR XXH_SCALAR
+#endif
+
 /*
  * Controls the alignment of the accumulator,
  * for compatibility with aligned vector loads, which are usually faster.
@@ -2898,16 +3640,26 @@ enum XXH_VECTOR_TYPE /* fake enum */ {
 #     define XXH_ACC_ALIGN 16
 #  elif XXH_VECTOR == XXH_AVX512  /* avx512 */
 #     define XXH_ACC_ALIGN 64
+#  elif XXH_VECTOR == XXH_SVE   /* sve */
+#     define XXH_ACC_ALIGN 64
 #  endif
 #endif
 
 #if defined(XXH_X86DISPATCH) || XXH_VECTOR == XXH_SSE2 \
     || XXH_VECTOR == XXH_AVX2 || XXH_VECTOR == XXH_AVX512
 #  define XXH_SEC_ALIGN XXH_ACC_ALIGN
+#elif XXH_VECTOR == XXH_SVE
+#  define XXH_SEC_ALIGN XXH_ACC_ALIGN
 #else
 #  define XXH_SEC_ALIGN 8
 #endif
 
+#if defined(__GNUC__) || defined(__clang__)
+#  define XXH_ALIASING __attribute__((may_alias))
+#else
+#  define XXH_ALIASING /* nothing */
+#endif
+
 /*
  * UGLY HACK:
  * GCC usually generates the best code with -O3 for xxHash.
@@ -2931,111 +3683,130 @@ enum XXH_VECTOR_TYPE /* fake enum */ {
  */
 #if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
   && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
-  && defined(__OPTIMIZE__) && !defined(__OPTIMIZE_SIZE__) /* respect -O0 and -Os */
+  && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */
 #  pragma GCC push_options
 #  pragma GCC optimize("-O2")
 #endif
 
-
 #if XXH_VECTOR == XXH_NEON
+
 /*
- * NEON's setup for vmlal_u32 is a little more complicated than it is on
- * SSE2, AVX2, and VSX.
- *
- * While PMULUDQ and VMULEUW both perform a mask, VMLAL.U32 performs an upcast.
- *
- * To do the same operation, the 128-bit 'Q' register needs to be split into
- * two 64-bit 'D' registers, performing this operation::
- *
- *   [                a                 |                 b                ]
- *            |              '---------. .--------'                |
- *            |                         x                          |
- *            |              .---------' '--------.                |
- *   [ a & 0xFFFFFFFF | b & 0xFFFFFFFF ],[    a >> 32     |     b >> 32    ]
+ * UGLY HACK: While AArch64 GCC on Linux does not seem to care, on macOS, GCC -O3
+ * optimizes out the entire hashLong loop because of the aliasing violation.
  *
- * Due to significant changes in aarch64, the fastest method for aarch64 is
- * completely different than the fastest method for ARMv7-A.
- *
- * ARMv7-A treats D registers as unions overlaying Q registers, so modifying
- * D11 will modify the high half of Q5. This is similar to how modifying AH
- * will only affect bits 8-15 of AX on x86.
- *
- * VZIP takes two registers, and puts even lanes in one register and odd lanes
- * in the other.
+ * However, GCC is also inefficient at load-store optimization with vld1q/vst1q,
+ * so the only option is to mark it as aliasing.
+ */
+typedef uint64x2_t xxh_aliasing_uint64x2_t XXH_ALIASING;
+
+/*!
+ * @internal
+ * @brief `vld1q_u64` but faster and alignment-safe.
  *
- * On ARMv7-A, this strangely modifies both parameters in place instead of
- * taking the usual 3-operand form.
+ * On AArch64, unaligned access is always safe, but on ARMv7-a, it is only
+ * *conditionally* safe (`vld1` has an alignment bit like `movdq[ua]` in x86).
  *
- * Therefore, if we want to do this, we can simply use a D-form VZIP.32 on the
- * lower and upper halves of the Q register to end up with the high and low
- * halves where we want - all in one instruction.
+ * GCC for AArch64 sees `vld1q_u8` as an intrinsic instead of a load, so it
+ * prohibits load-store optimizations. Therefore, a direct dereference is used.
  *
- *   vzip.32   d10, d11       @ d10 = { d10[0], d11[0] }; d11 = { d10[1], d11[1] }
+ * Otherwise, `vld1q_u8` is used with `vreinterpretq_u8_u64` to do a safe
+ * unaligned load.
+ */
+#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)
+XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) /* silence -Wcast-align */
+{
+    return *(xxh_aliasing_uint64x2_t const *)ptr;
+}
+#else
+XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr)
+{
+    return vreinterpretq_u64_u8(vld1q_u8((uint8_t const*)ptr));
+}
+#endif
+
+/*!
+ * @internal
+ * @brief `vmlal_u32` on low and high halves of a vector.
  *
- * Unfortunately we need inline assembly for this: Instructions modifying two
- * registers at once is not possible in GCC or Clang's IR, and they have to
- * create a copy.
+ * This is a workaround for AArch64 GCC < 11 which implemented arm_neon.h with
+ * inline assembly and were therefore incapable of merging the `vget_{low, high}_u32`
+ * with `vmlal_u32`.
+ */
+#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 11
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+    /* Inline assembly is the only way */
+    __asm__("umlal   %0.2d, %1.2s, %2.2s" : "+w" (acc) : "w" (lhs), "w" (rhs));
+    return acc;
+}
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+    /* This intrinsic works as expected */
+    return vmlal_high_u32(acc, lhs, rhs);
+}
+#else
+/* Portable intrinsic versions */
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+    return vmlal_u32(acc, vget_low_u32(lhs), vget_low_u32(rhs));
+}
+/*! @copydoc XXH_vmlal_low_u32
+ * Assume the compiler converts this to vmlal_high_u32 on aarch64 */
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+    return vmlal_u32(acc, vget_high_u32(lhs), vget_high_u32(rhs));
+}
+#endif
+
+/*!
+ * @ingroup tuning
+ * @brief Controls the NEON to scalar ratio for XXH3
  *
- * aarch64 requires a different approach.
+ * This can be set to 2, 4, 6, or 8.
  *
- * In order to make it easier to write a decent compiler for aarch64, many
- * quirks were removed, such as conditional execution.
+ * ARM Cortex CPUs are _very_ sensitive to how their pipelines are used.
  *
- * NEON was also affected by this.
+ * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but only 2 of those
+ * can be NEON. If you are only using NEON instructions, you are only using 2/3 of the CPU
+ * bandwidth.
  *
- * aarch64 cannot access the high bits of a Q-form register, and writes to a
- * D-form register zero the high bits, similar to how writes to W-form scalar
- * registers (or DWORD registers on x86_64) work.
+ * This is even more noticeable on the more advanced cores like the Cortex-A76 which
+ * can dispatch 8 micro-ops per cycle, but still only 2 NEON micro-ops at once.
  *
- * The formerly free vget_high intrinsics now require a vext (with a few
- * exceptions)
+ * Therefore, to make the most out of the pipeline, it is beneficial to run 6 NEON lanes
+ * and 2 scalar lanes, which is chosen by default.
  *
- * Additionally, VZIP was replaced by ZIP1 and ZIP2, which are the equivalent
- * of PUNPCKL* and PUNPCKH* in SSE, respectively, in order to only modify one
- * operand.
+ * This does not apply to Apple processors or 32-bit processors, which run better with
+ * full NEON. These will default to 8. Additionally, size-optimized builds run 8 lanes.
  *
- * The equivalent of the VZIP.32 on the lower and upper halves would be this
- * mess:
+ * This change benefits CPUs with large micro-op buffers without negatively affecting
+ * most other CPUs:
  *
- *   ext     v2.4s, v0.4s, v0.4s, #2 // v2 = { v0[2], v0[3], v0[0], v0[1] }
- *   zip1    v1.2s, v0.2s, v2.2s     // v1 = { v0[0], v2[0] }
- *   zip2    v0.2s, v0.2s, v1.2s     // v0 = { v0[1], v2[1] }
+ *  | Chipset               | Dispatch type       | NEON only | 6:2 hybrid | Diff. |
+ *  |:----------------------|:--------------------|----------:|-----------:|------:|
+ *  | Snapdragon 730 (A76)  | 2 NEON/8 micro-ops  |  8.8 GB/s |  10.1 GB/s |  ~16% |
+ *  | Snapdragon 835 (A73)  | 2 NEON/3 micro-ops  |  5.1 GB/s |   5.3 GB/s |   ~5% |
+ *  | Marvell PXA1928 (A53) | In-order dual-issue |  1.9 GB/s |   1.9 GB/s |    0% |
+ *  | Apple M1              | 4 NEON/8 micro-ops  | 37.3 GB/s |  36.1 GB/s |  ~-3% |
  *
- * Instead, we use a literal downcast, vmovn_u64 (XTN), and vshrn_n_u64 (SHRN):
+ * It also seems to fix some bad codegen on GCC, making it almost as fast as clang.
  *
- *   shrn    v1.2s, v0.2d, #32  // v1 = (uint32x2_t)(v0 >> 32);
- *   xtn     v0.2s, v0.2d       // v0 = (uint32x2_t)(v0 & 0xFFFFFFFF);
+ * When using WASM SIMD128, if this is 2 or 6, SIMDe will scalarize 2 of the lanes meaning
+ * it effectively becomes worse 4.
  *
- * This is available on ARMv7-A, but is less efficient than a single VZIP.32.
+ * @see XXH3_accumulate_512_neon()
  */
-
-/*!
- * Function-like macro:
- * void XXH_SPLIT_IN_PLACE(uint64x2_t &in, uint32x2_t &outLo, uint32x2_t &outHi)
- * {
- *     outLo = (uint32x2_t)(in & 0xFFFFFFFF);
- *     outHi = (uint32x2_t)(in >> 32);
- *     in = UNDEFINED;
- * }
- */
-# if !defined(XXH_NO_VZIP_HACK) /* define to disable */ \
-   && defined(__GNUC__) \
-   && !defined(__aarch64__) && !defined(__arm64__) && !defined(_M_ARM64)
-#  define XXH_SPLIT_IN_PLACE(in, outLo, outHi)                                              \
-    do {                                                                                    \
-      /* Undocumented GCC/Clang operand modifier: %e0 = lower D half, %f0 = upper D half */ \
-      /* https://github.com/gcc-mirror/gcc/blob/38cf91e5/gcc/config/arm/arm.c#L22486 */     \
-      /* https://github.com/llvm-mirror/llvm/blob/2c4ca683/lib/Target/ARM/ARMAsmPrinter.cpp#L399 */ \
-      __asm__("vzip.32  %e0, %f0" : "+w" (in));                                             \
-      (outLo) = vget_low_u32 (vreinterpretq_u32_u64(in));                                   \
-      (outHi) = vget_high_u32(vreinterpretq_u32_u64(in));                                   \
-   } while (0)
-# else
-#  define XXH_SPLIT_IN_PLACE(in, outLo, outHi)                                            \
-    do {                                                                                  \
-      (outLo) = vmovn_u64    (in);                                                        \
-      (outHi) = vshrn_n_u64  ((in), 32);                                                  \
-    } while (0)
+# ifndef XXH3_NEON_LANES
+#  if (defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) \
+   && !defined(__APPLE__) && XXH_SIZE_OPT <= 0
+#   define XXH3_NEON_LANES 6
+#  else
+#   define XXH3_NEON_LANES XXH_ACC_NB
+#  endif
 # endif
 #endif  /* XXH_VECTOR == XXH_NEON */
 
@@ -3048,27 +3819,42 @@ enum XXH_VECTOR_TYPE /* fake enum */ {
  * inconsistent intrinsics, spotty coverage, and multiple endiannesses.
  */
 #if XXH_VECTOR == XXH_VSX
+/* Annoyingly, these headers _may_ define three macros: `bool`, `vector`,
+ * and `pixel`. This is a problem for obvious reasons.
+ *
+ * These keywords are unnecessary; the spec literally says they are
+ * equivalent to `__bool`, `__vector`, and `__pixel` and may be undef'd
+ * after including the header.
+ *
+ * We use pragma push_macro/pop_macro to keep the namespace clean. */
+#  pragma push_macro("bool")
+#  pragma push_macro("vector")
+#  pragma push_macro("pixel")
+/* silence potential macro redefined warnings */
+#  undef bool
+#  undef vector
+#  undef pixel
+
 #  if defined(__s390x__)
 #    include <s390intrin.h>
 #  else
-/* gcc's altivec.h can have the unwanted consequence to unconditionally
- * #define bool, vector, and pixel keywords,
- * with bad consequences for programs already using these keywords for other purposes.
- * The paragraph defining these macros is skipped when __APPLE_ALTIVEC__ is defined.
- * __APPLE_ALTIVEC__ is _generally_ defined automatically by the compiler,
- * but it seems that, in some cases, it isn't.
- * Force the build macro to be defined, so that keywords are not altered.
- */
-#    if defined(__GNUC__) && !defined(__APPLE_ALTIVEC__)
-#      define __APPLE_ALTIVEC__
-#    endif
 #    include <altivec.h>
 #  endif
 
+/* Restore the original macro values, if applicable. */
+#  pragma pop_macro("pixel")
+#  pragma pop_macro("vector")
+#  pragma pop_macro("bool")
+
 typedef __vector unsigned long long xxh_u64x2;
 typedef __vector unsigned char xxh_u8x16;
 typedef __vector unsigned xxh_u32x4;
 
+/*
+ * UGLY HACK: Similar to aarch64 macOS GCC, s390x GCC has the same aliasing issue.
+ */
+typedef xxh_u64x2 xxh_aliasing_u64x2 XXH_ALIASING;
+
 # ifndef XXH_VSX_BE
 #  if defined(__BIG_ENDIAN__) \
   || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
@@ -3120,8 +3906,9 @@ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr)
  /* s390x is always big endian, no issue on this platform */
 #  define XXH_vec_mulo vec_mulo
 #  define XXH_vec_mule vec_mule
-# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw)
+# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) && !defined(__ibmxl__)
 /* Clang has a better way to control this, we can just use the builtin which doesn't swap. */
+ /* The IBM XL Compiler (which defined __clang__) only implements the vec_* operations */
 #  define XXH_vec_mulo __builtin_altivec_vmulouw
 #  define XXH_vec_mule __builtin_altivec_vmuleuw
 # else
@@ -3142,13 +3929,28 @@ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mule(xxh_u32x4 a, xxh_u32x4 b)
 # endif /* XXH_vec_mulo, XXH_vec_mule */
 #endif /* XXH_VECTOR == XXH_VSX */
 
+#if XXH_VECTOR == XXH_SVE
+#define ACCRND(acc, offset) \
+do { \
+    svuint64_t input_vec = svld1_u64(mask, xinput + offset);         \
+    svuint64_t secret_vec = svld1_u64(mask, xsecret + offset);       \
+    svuint64_t mixed = sveor_u64_x(mask, secret_vec, input_vec);     \
+    svuint64_t swapped = svtbl_u64(input_vec, kSwap);                \
+    svuint64_t mixed_lo = svextw_u64_x(mask, mixed);                 \
+    svuint64_t mixed_hi = svlsr_n_u64_x(mask, mixed, 32);            \
+    svuint64_t mul = svmad_u64_x(mask, mixed_lo, mixed_hi, swapped); \
+    acc = svadd_u64_x(mask, acc, mul);                               \
+} while (0)
+#endif /* XXH_VECTOR == XXH_SVE */
 
 /* prefetch
  * can be disabled, by declaring XXH_NO_PREFETCH build macro */
 #if defined(XXH_NO_PREFETCH)
 #  define XXH_PREFETCH(ptr)  (void)(ptr)  /* disabled */
 #else
-#  if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))  /* _mm_prefetch() not defined outside of x86/x64 */
+#  if XXH_SIZE_OPT >= 1
+#    define XXH_PREFETCH(ptr) (void)(ptr)
+#  elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))  /* _mm_prefetch() not defined outside of x86/x64 */
 #    include <mmintrin.h>   /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */
 #    define XXH_PREFETCH(ptr)  _mm_prefetch((const char*)(ptr), _MM_HINT_T0)
 #  elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) )
@@ -3185,6 +3987,8 @@ XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = {
     0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
 };
 
+static const xxh_u64 PRIME_MX1 = 0x165667919E3779F9ULL;  /*!< 0b0001011001010110011001111001000110011110001101110111100111111001 */
+static const xxh_u64 PRIME_MX2 = 0x9FB21C651E98DF25ULL;  /*!< 0b1001111110110010000111000110010100011110100110001101111100100101 */
 
 #ifdef XXH_OLD_NAMES
 #  define kSecret XXH3_kSecret
@@ -3213,7 +4017,6 @@ XXH_mult32to64(xxh_u64 x, xxh_u64 y)
    return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF);
 }
 #elif defined(_MSC_VER) && defined(_M_IX86)
-#    include <intrin.h>
 #    define XXH_mult32to64(x, y) __emulu((unsigned)(x), (unsigned)(y))
 #else
 /*
@@ -3253,7 +4056,7 @@ XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
      * In that case it is best to use the portable one.
      * https://github.com/Cyan4973/xxHash/issues/211#issuecomment-515575677
      */
-#if defined(__GNUC__) && !defined(__wasm__) \
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) \
     && defined(__SIZEOF_INT128__) \
     || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128)
 
@@ -3270,7 +4073,7 @@ XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
      *
      * This compiles to single operand MUL on x64.
      */
-#elif defined(_M_X64) || defined(_M_IA64)
+#elif (defined(_M_X64) || defined(_M_IA64)) && !defined(_M_ARM64EC)
 
 #ifndef _MSC_VER
 #   pragma intrinsic(_umul128)
@@ -3287,7 +4090,7 @@ XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
      *
      * This compiles to the same MUL + UMULH as GCC/Clang's __uint128_t method.
      */
-#elif defined(_M_ARM64)
+#elif defined(_M_ARM64) || defined(_M_ARM64EC)
 
 #ifndef _MSC_VER
 #   pragma intrinsic(__umulh)
@@ -3377,7 +4180,7 @@ XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs)
 }
 
 /*! Seems to produce slightly better code on GCC for some reason. */
-XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift)
+XXH_FORCE_INLINE XXH_CONSTF xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift)
 {
     XXH_ASSERT(0 <= shift && shift < 64);
     return v64 ^ (v64 >> shift);
@@ -3390,7 +4193,7 @@ XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift)
 static XXH64_hash_t XXH3_avalanche(xxh_u64 h64)
 {
     h64 = XXH_xorshift64(h64, 37);
-    h64 *= 0x165667919E3779F9ULL;
+    h64 *= PRIME_MX1;
     h64 = XXH_xorshift64(h64, 32);
     return h64;
 }
@@ -3404,9 +4207,9 @@ static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len)
 {
     /* this mix is inspired by Pelle Evensen's rrmxmx */
     h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24);
-    h64 *= 0x9FB21C651E98DF25ULL;
+    h64 *= PRIME_MX2;
     h64 ^= (h64 >> 35) + len ;
-    h64 *= 0x9FB21C651E98DF25ULL;
+    h64 *= PRIME_MX2;
     return XXH_xorshift64(h64, 28);
 }
 
@@ -3444,7 +4247,7 @@ static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len)
  *
  * This adds an extra layer of strength for custom secrets.
  */
-XXH_FORCE_INLINE XXH64_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
 XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(input != NULL);
@@ -3466,7 +4269,7 @@ XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_h
     }
 }
 
-XXH_FORCE_INLINE XXH64_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
 XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(input != NULL);
@@ -3482,7 +4285,7 @@ XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_h
     }
 }
 
-XXH_FORCE_INLINE XXH64_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
 XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(input != NULL);
@@ -3499,7 +4302,7 @@ XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_
     }
 }
 
-XXH_FORCE_INLINE XXH64_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
 XXH3_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(len <= 16);
@@ -3569,7 +4372,7 @@ XXH_FORCE_INLINE xxh_u64 XXH3_mix16B(const xxh_u8* XXH_RESTRICT input,
 }
 
 /* For mid range keys, XXH3 uses a Mum-hash variant. */
-XXH_FORCE_INLINE XXH64_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
 XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
                      const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
                      XXH64_hash_t seed)
@@ -3578,6 +4381,14 @@ XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
     XXH_ASSERT(16 < len && len <= 128);
 
     {   xxh_u64 acc = len * XXH_PRIME64_1;
+#if XXH_SIZE_OPT >= 1
+        /* Smaller and cleaner, but slightly slower. */
+        unsigned int i = (unsigned int)(len - 1) / 32;
+        do {
+            acc += XXH3_mix16B(input+16 * i, secret+32*i, seed);
+            acc += XXH3_mix16B(input+len-16*(i+1), secret+32*i+16, seed);
+        } while (i-- != 0);
+#else
         if (len > 32) {
             if (len > 64) {
                 if (len > 96) {
@@ -3592,14 +4403,14 @@ XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
         }
         acc += XXH3_mix16B(input+0, secret+0, seed);
         acc += XXH3_mix16B(input+len-16, secret+16, seed);
-
+#endif
         return XXH3_avalanche(acc);
     }
 }
 
 #define XXH3_MIDSIZE_MAX 240
 
-XXH_NO_INLINE XXH64_hash_t
+XXH_NO_INLINE XXH_PUREF XXH64_hash_t
 XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
                       const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
                       XXH64_hash_t seed)
@@ -3611,13 +4422,17 @@ XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
     #define XXH3_MIDSIZE_LASTOFFSET  17
 
     {   xxh_u64 acc = len * XXH_PRIME64_1;
-        int const nbRounds = (int)len / 16;
-        int i;
+        xxh_u64 acc_end;
+        unsigned int const nbRounds = (unsigned int)len / 16;
+        unsigned int i;
+        XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
         for (i=0; i<8; i++) {
             acc += XXH3_mix16B(input+(16*i), secret+(16*i), seed);
         }
-        acc = XXH3_avalanche(acc);
+        /* last bytes */
+        acc_end = XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed);
         XXH_ASSERT(nbRounds >= 8);
+        acc = XXH3_avalanche(acc);
 #if defined(__clang__)                                /* Clang */ \
     && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \
     && !defined(XXH_ENABLE_AUTOVECTORIZE)             /* Define to disable */
@@ -3644,11 +4459,13 @@ XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
         #pragma clang loop vectorize(disable)
 #endif
         for (i=8 ; i < nbRounds; i++) {
-            acc += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed);
+            /*
+             * Prevents clang for unrolling the acc loop and interleaving with this one.
+             */
+            XXH_COMPILER_GUARD(acc);
+            acc_end += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed);
         }
-        /* last bytes */
-        acc += XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed);
-        return XXH3_avalanche(acc);
+        return XXH3_avalanche(acc + acc_end);
     }
 }
 
@@ -3664,6 +4481,47 @@ XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
 #  define ACC_NB XXH_ACC_NB
 #endif
 
+#ifndef XXH_PREFETCH_DIST
+#  ifdef __clang__
+#    define XXH_PREFETCH_DIST 320
+#  else
+#    if (XXH_VECTOR == XXH_AVX512)
+#      define XXH_PREFETCH_DIST 512
+#    else
+#      define XXH_PREFETCH_DIST 384
+#    endif
+#  endif  /* __clang__ */
+#endif  /* XXH_PREFETCH_DIST */
+
+/*
+ * These macros are to generate an XXH3_accumulate() function.
+ * The two arguments select the name suffix and target attribute.
+ *
+ * The name of this symbol is XXH3_accumulate_<name>() and it calls
+ * XXH3_accumulate_512_<name>().
+ *
+ * It may be useful to hand implement this function if the compiler fails to
+ * optimize the inline function.
+ */
+#define XXH3_ACCUMULATE_TEMPLATE(name)                      \
+void                                                        \
+XXH3_accumulate_##name(xxh_u64* XXH_RESTRICT acc,           \
+                       const xxh_u8* XXH_RESTRICT input,    \
+                       const xxh_u8* XXH_RESTRICT secret,   \
+                       size_t nbStripes)                    \
+{                                                           \
+    size_t n;                                               \
+    for (n = 0; n < nbStripes; n++ ) {                      \
+        const xxh_u8* const in = input + n*XXH_STRIPE_LEN;  \
+        XXH_PREFETCH(in + XXH_PREFETCH_DIST);               \
+        XXH3_accumulate_512_##name(                         \
+                 acc,                                       \
+                 in,                                        \
+                 secret + n*XXH_SECRET_CONSUME_RATE);       \
+    }                                                       \
+}
+
+
 XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64)
 {
     if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64);
@@ -3684,6 +4542,7 @@ XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64)
     typedef long long xxh_i64;
 #endif
 
+
 /*
  * XXH3_accumulate_512 is the tightest loop for long inputs, and it is the most optimized.
  *
@@ -3731,7 +4590,7 @@ XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc,
         /* data_key    = data_vec ^ key_vec; */
         __m512i const data_key    = _mm512_xor_si512     (data_vec, key_vec);
         /* data_key_lo = data_key >> 32; */
-        __m512i const data_key_lo = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1));
+        __m512i const data_key_lo = _mm512_srli_epi64 (data_key, 32);
         /* product     = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
         __m512i const product     = _mm512_mul_epu32     (data_key, data_key_lo);
         /* xacc[0] += swap(data_vec); */
@@ -3741,6 +4600,7 @@ XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc,
         *xacc = _mm512_add_epi64(product, sum);
     }
 }
+XXH_FORCE_INLINE XXH_TARGET_AVX512 XXH3_ACCUMULATE_TEMPLATE(avx512)
 
 /*
  * XXH3_scrambleAcc: Scrambles the accumulators to improve mixing.
@@ -3774,13 +4634,12 @@ XXH3_scrambleAcc_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
         /* xacc[0] ^= (xacc[0] >> 47) */
         __m512i const acc_vec     = *xacc;
         __m512i const shifted     = _mm512_srli_epi64    (acc_vec, 47);
-        __m512i const data_vec    = _mm512_xor_si512     (acc_vec, shifted);
         /* xacc[0] ^= secret; */
         __m512i const key_vec     = _mm512_loadu_si512   (secret);
-        __m512i const data_key    = _mm512_xor_si512     (data_vec, key_vec);
+        __m512i const data_key    = _mm512_ternarylogic_epi32(key_vec, acc_vec, shifted, 0x96 /* key_vec ^ acc_vec ^ shifted */);
 
         /* xacc[0] *= XXH_PRIME32_1; */
-        __m512i const data_key_hi = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1));
+        __m512i const data_key_hi = _mm512_srli_epi64 (data_key, 32);
         __m512i const prod_lo     = _mm512_mul_epu32     (data_key, prime32);
         __m512i const prod_hi     = _mm512_mul_epu32     (data_key_hi, prime32);
         *xacc = _mm512_add_epi64(prod_lo, _mm512_slli_epi64(prod_hi, 32));
@@ -3795,7 +4654,8 @@ XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
     XXH_ASSERT(((size_t)customSecret & 63) == 0);
     (void)(&XXH_writeLE64);
     {   int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i);
-        __m512i const seed = _mm512_mask_set1_epi64(_mm512_set1_epi64((xxh_i64)seed64), 0xAA, (xxh_i64)(0U - seed64));
+        __m512i const seed_pos = _mm512_set1_epi64((xxh_i64)seed64);
+        __m512i const seed     = _mm512_mask_sub_epi64(seed_pos, 0xAA, _mm512_set1_epi8(0), seed_pos);
 
         const __m512i* const src  = (const __m512i*) ((const void*) XXH3_kSecret);
               __m512i* const dest = (      __m512i*) customSecret;
@@ -3803,14 +4663,7 @@ XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
         XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */
         XXH_ASSERT(((size_t)dest & 63) == 0);
         for (i=0; i < nbRounds; ++i) {
-            /* GCC has a bug, _mm512_stream_load_si512 accepts 'void*', not 'void const*',
-             * this will warn "discards 'const' qualifier". */
-            union {
-                const __m512i* cp;
-                void* p;
-            } remote_const_void;
-            remote_const_void.cp = src + i;
-            dest[i] = _mm512_add_epi64(_mm512_stream_load_si512(remote_const_void.p), seed);
+            dest[i] = _mm512_add_epi64(_mm512_load_si512(src + i), seed);
     }   }
 }
 
@@ -3846,7 +4699,7 @@ XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc,
             /* data_key    = data_vec ^ key_vec; */
             __m256i const data_key    = _mm256_xor_si256     (data_vec, key_vec);
             /* data_key_lo = data_key >> 32; */
-            __m256i const data_key_lo = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+            __m256i const data_key_lo = _mm256_srli_epi64 (data_key, 32);
             /* product     = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
             __m256i const product     = _mm256_mul_epu32     (data_key, data_key_lo);
             /* xacc[i] += swap(data_vec); */
@@ -3856,6 +4709,7 @@ XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc,
             xacc[i] = _mm256_add_epi64(product, sum);
     }   }
 }
+XXH_FORCE_INLINE XXH_TARGET_AVX2 XXH3_ACCUMULATE_TEMPLATE(avx2)
 
 XXH_FORCE_INLINE XXH_TARGET_AVX2 void
 XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
@@ -3878,7 +4732,7 @@ XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
             __m256i const data_key    = _mm256_xor_si256     (data_vec, key_vec);
 
             /* xacc[i] *= XXH_PRIME32_1; */
-            __m256i const data_key_hi = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+            __m256i const data_key_hi = _mm256_srli_epi64 (data_key, 32);
             __m256i const prod_lo     = _mm256_mul_epu32     (data_key, prime32);
             __m256i const prod_hi     = _mm256_mul_epu32     (data_key_hi, prime32);
             xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32));
@@ -3910,12 +4764,12 @@ XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_initCustomSecret_avx2(void* XXH_RESTR
         XXH_ASSERT(((size_t)dest & 31) == 0);
 
         /* GCC -O2 need unroll loop manually */
-        dest[0] = _mm256_add_epi64(_mm256_stream_load_si256(src+0), seed);
-        dest[1] = _mm256_add_epi64(_mm256_stream_load_si256(src+1), seed);
-        dest[2] = _mm256_add_epi64(_mm256_stream_load_si256(src+2), seed);
-        dest[3] = _mm256_add_epi64(_mm256_stream_load_si256(src+3), seed);
-        dest[4] = _mm256_add_epi64(_mm256_stream_load_si256(src+4), seed);
-        dest[5] = _mm256_add_epi64(_mm256_stream_load_si256(src+5), seed);
+        dest[0] = _mm256_add_epi64(_mm256_load_si256(src+0), seed);
+        dest[1] = _mm256_add_epi64(_mm256_load_si256(src+1), seed);
+        dest[2] = _mm256_add_epi64(_mm256_load_si256(src+2), seed);
+        dest[3] = _mm256_add_epi64(_mm256_load_si256(src+3), seed);
+        dest[4] = _mm256_add_epi64(_mm256_load_si256(src+4), seed);
+        dest[5] = _mm256_add_epi64(_mm256_load_si256(src+5), seed);
     }
 }
 
@@ -3962,6 +4816,7 @@ XXH3_accumulate_512_sse2( void* XXH_RESTRICT acc,
             xacc[i] = _mm_add_epi64(product, sum);
     }   }
 }
+XXH_FORCE_INLINE XXH_TARGET_SSE2 XXH3_ACCUMULATE_TEMPLATE(sse2)
 
 XXH_FORCE_INLINE XXH_TARGET_SSE2 void
 XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
@@ -4029,96 +4884,222 @@ XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_initCustomSecret_sse2(void* XXH_RESTR
 
 #if (XXH_VECTOR == XXH_NEON)
 
+/* forward declarations for the scalar routines */
+XXH_FORCE_INLINE void
+XXH3_scalarRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT input,
+                 void const* XXH_RESTRICT secret, size_t lane);
+
+XXH_FORCE_INLINE void
+XXH3_scalarScrambleRound(void* XXH_RESTRICT acc,
+                         void const* XXH_RESTRICT secret, size_t lane);
+
+/*!
+ * @internal
+ * @brief The bulk processing loop for NEON and WASM SIMD128.
+ *
+ * The NEON code path is actually partially scalar when running on AArch64. This
+ * is to optimize the pipelining and can have up to 15% speedup depending on the
+ * CPU, and it also mitigates some GCC codegen issues.
+ *
+ * @see XXH3_NEON_LANES for configuring this and details about this optimization.
+ *
+ * NEON's 32-bit to 64-bit long multiply takes a half vector of 32-bit
+ * integers instead of the other platforms which mask full 64-bit vectors,
+ * so the setup is more complicated than just shifting right.
+ *
+ * Additionally, there is an optimization for 4 lanes at once noted below.
+ *
+ * Since, as stated, the most optimal amount of lanes for Cortexes is 6,
+ * there needs to be *three* versions of the accumulate operation used
+ * for the remaining 2 lanes.
+ *
+ * WASM's SIMD128 uses SIMDe's arm_neon.h polyfill because the intrinsics overlap
+ * nearly perfectly.
+ */
+
 XXH_FORCE_INLINE void
 XXH3_accumulate_512_neon( void* XXH_RESTRICT acc,
                     const void* XXH_RESTRICT input,
                     const void* XXH_RESTRICT secret)
 {
     XXH_ASSERT((((size_t)acc) & 15) == 0);
-    {
-        uint64x2_t* const xacc = (uint64x2_t *) acc;
+    XXH_STATIC_ASSERT(XXH3_NEON_LANES > 0 && XXH3_NEON_LANES <= XXH_ACC_NB && XXH3_NEON_LANES % 2 == 0);
+    {   /* GCC for darwin arm64 does not like aliasing here */
+        xxh_aliasing_uint64x2_t* const xacc = (xxh_aliasing_uint64x2_t*) acc;
         /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */
-        uint8_t const* const xinput = (const uint8_t *) input;
-        uint8_t const* const xsecret  = (const uint8_t *) secret;
+        uint8_t const* xinput = (const uint8_t *) input;
+        uint8_t const* xsecret  = (const uint8_t *) secret;
 
         size_t i;
-        for (i=0; i < XXH_STRIPE_LEN / sizeof(uint64x2_t); i++) {
+#ifdef __wasm_simd128__
+        /*
+         * On WASM SIMD128, Clang emits direct address loads when XXH3_kSecret
+         * is constant propagated, which results in it converting it to this
+         * inside the loop:
+         *
+         *    a = v128.load(XXH3_kSecret +  0 + $secret_offset, offset = 0)
+         *    b = v128.load(XXH3_kSecret + 16 + $secret_offset, offset = 0)
+         *    ...
+         *
+         * This requires a full 32-bit address immediate (and therefore a 6 byte
+         * instruction) as well as an add for each offset.
+         *
+         * Putting an asm guard prevents it from folding (at the cost of losing
+         * the alignment hint), and uses the free offset in `v128.load` instead
+         * of adding secret_offset each time which overall reduces code size by
+         * about a kilobyte and improves performance.
+         */
+        XXH_COMPILER_GUARD(xsecret);
+#endif
+        /* Scalar lanes use the normal scalarRound routine */
+        for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) {
+            XXH3_scalarRound(acc, input, secret, i);
+        }
+        i = 0;
+        /* 4 NEON lanes at a time. */
+        for (; i+1 < XXH3_NEON_LANES / 2; i+=2) {
             /* data_vec = xinput[i]; */
-            uint8x16_t data_vec    = vld1q_u8(xinput  + (i * 16));
+            uint64x2_t data_vec_1 = XXH_vld1q_u64(xinput  + (i * 16));
+            uint64x2_t data_vec_2 = XXH_vld1q_u64(xinput  + ((i+1) * 16));
             /* key_vec  = xsecret[i];  */
-            uint8x16_t key_vec     = vld1q_u8(xsecret + (i * 16));
-            uint64x2_t data_key;
-            uint32x2_t data_key_lo, data_key_hi;
-            /* xacc[i] += swap(data_vec); */
-            uint64x2_t const data64  = vreinterpretq_u64_u8(data_vec);
-            uint64x2_t const swapped = vextq_u64(data64, data64, 1);
-            xacc[i] = vaddq_u64 (xacc[i], swapped);
+            uint64x2_t key_vec_1  = XXH_vld1q_u64(xsecret + (i * 16));
+            uint64x2_t key_vec_2  = XXH_vld1q_u64(xsecret + ((i+1) * 16));
+            /* data_swap = swap(data_vec) */
+            uint64x2_t data_swap_1 = vextq_u64(data_vec_1, data_vec_1, 1);
+            uint64x2_t data_swap_2 = vextq_u64(data_vec_2, data_vec_2, 1);
             /* data_key = data_vec ^ key_vec; */
-            data_key = vreinterpretq_u64_u8(veorq_u8(data_vec, key_vec));
-            /* data_key_lo = (uint32x2_t) (data_key & 0xFFFFFFFF);
-             * data_key_hi = (uint32x2_t) (data_key >> 32);
-             * data_key = UNDEFINED; */
-            XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi);
-            /* xacc[i] += (uint64x2_t) data_key_lo * (uint64x2_t) data_key_hi; */
-            xacc[i] = vmlal_u32 (xacc[i], data_key_lo, data_key_hi);
+            uint64x2_t data_key_1 = veorq_u64(data_vec_1, key_vec_1);
+            uint64x2_t data_key_2 = veorq_u64(data_vec_2, key_vec_2);
 
+            /*
+             * If we reinterpret the 64x2 vectors as 32x4 vectors, we can use a
+             * de-interleave operation for 4 lanes in 1 step with `vuzpq_u32` to
+             * get one vector with the low 32 bits of each lane, and one vector
+             * with the high 32 bits of each lane.
+             *
+             * The intrinsic returns a double vector because the original ARMv7-a
+             * instruction modified both arguments in place. AArch64 and SIMD128 emit
+             * two instructions from this intrinsic.
+             *
+             *  [ dk11L | dk11H | dk12L | dk12H ] -> [ dk11L | dk12L | dk21L | dk22L ]
+             *  [ dk21L | dk21H | dk22L | dk22H ] -> [ dk11H | dk12H | dk21H | dk22H ]
+             */
+            uint32x4x2_t unzipped = vuzpq_u32(
+                vreinterpretq_u32_u64(data_key_1),
+                vreinterpretq_u32_u64(data_key_2)
+            );
+            /* data_key_lo = data_key & 0xFFFFFFFF */
+            uint32x4_t data_key_lo = unzipped.val[0];
+            /* data_key_hi = data_key >> 32 */
+            uint32x4_t data_key_hi = unzipped.val[1];
+            /*
+             * Then, we can split the vectors horizontally and multiply which, as for most
+             * widening intrinsics, have a variant that works on both high half vectors
+             * for free on AArch64. A similar instruction is available on SIMD128.
+             *
+             * sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi
+             */
+            uint64x2_t sum_1 = XXH_vmlal_low_u32(data_swap_1, data_key_lo, data_key_hi);
+            uint64x2_t sum_2 = XXH_vmlal_high_u32(data_swap_2, data_key_lo, data_key_hi);
+            /*
+             * Clang reorders
+             *    a += b * c;     // umlal   swap.2d, dkl.2s, dkh.2s
+             *    c += a;         // add     acc.2d, acc.2d, swap.2d
+             * to
+             *    c += a;         // add     acc.2d, acc.2d, swap.2d
+             *    c += b * c;     // umlal   acc.2d, dkl.2s, dkh.2s
+             *
+             * While it would make sense in theory since the addition is faster,
+             * for reasons likely related to umlal being limited to certain NEON
+             * pipelines, this is worse. A compiler guard fixes this.
+             */
+            XXH_COMPILER_GUARD_CLANG_NEON(sum_1);
+            XXH_COMPILER_GUARD_CLANG_NEON(sum_2);
+            /* xacc[i] = acc_vec + sum; */
+            xacc[i]   = vaddq_u64(xacc[i], sum_1);
+            xacc[i+1] = vaddq_u64(xacc[i+1], sum_2);
+        }
+        /* Operate on the remaining NEON lanes 2 at a time. */
+        for (; i < XXH3_NEON_LANES / 2; i++) {
+            /* data_vec = xinput[i]; */
+            uint64x2_t data_vec = XXH_vld1q_u64(xinput  + (i * 16));
+            /* key_vec  = xsecret[i];  */
+            uint64x2_t key_vec  = XXH_vld1q_u64(xsecret + (i * 16));
+            /* acc_vec_2 = swap(data_vec) */
+            uint64x2_t data_swap = vextq_u64(data_vec, data_vec, 1);
+            /* data_key = data_vec ^ key_vec; */
+            uint64x2_t data_key = veorq_u64(data_vec, key_vec);
+            /* For two lanes, just use VMOVN and VSHRN. */
+            /* data_key_lo = data_key & 0xFFFFFFFF; */
+            uint32x2_t data_key_lo = vmovn_u64(data_key);
+            /* data_key_hi = data_key >> 32; */
+            uint32x2_t data_key_hi = vshrn_n_u64(data_key, 32);
+            /* sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi; */
+            uint64x2_t sum = vmlal_u32(data_swap, data_key_lo, data_key_hi);
+            /* Same Clang workaround as before */
+            XXH_COMPILER_GUARD_CLANG_NEON(sum);
+            /* xacc[i] = acc_vec + sum; */
+            xacc[i] = vaddq_u64 (xacc[i], sum);
         }
     }
 }
+XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(neon)
 
 XXH_FORCE_INLINE void
 XXH3_scrambleAcc_neon(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
 {
     XXH_ASSERT((((size_t)acc) & 15) == 0);
 
-    {   uint64x2_t* xacc       = (uint64x2_t*) acc;
+    {   xxh_aliasing_uint64x2_t* xacc       = (xxh_aliasing_uint64x2_t*) acc;
         uint8_t const* xsecret = (uint8_t const*) secret;
-        uint32x2_t prime       = vdup_n_u32 (XXH_PRIME32_1);
 
         size_t i;
-        for (i=0; i < XXH_STRIPE_LEN/sizeof(uint64x2_t); i++) {
+        /* WASM uses operator overloads and doesn't need these. */
+#ifndef __wasm_simd128__
+        /* { prime32_1, prime32_1 } */
+        uint32x2_t const kPrimeLo = vdup_n_u32(XXH_PRIME32_1);
+        /* { 0, prime32_1, 0, prime32_1 } */
+        uint32x4_t const kPrimeHi = vreinterpretq_u32_u64(vdupq_n_u64((xxh_u64)XXH_PRIME32_1 << 32));
+#endif
+
+        /* AArch64 uses both scalar and neon at the same time */
+        for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) {
+            XXH3_scalarScrambleRound(acc, secret, i);
+        }
+        for (i=0; i < XXH3_NEON_LANES / 2; i++) {
             /* xacc[i] ^= (xacc[i] >> 47); */
             uint64x2_t acc_vec  = xacc[i];
-            uint64x2_t shifted  = vshrq_n_u64 (acc_vec, 47);
-            uint64x2_t data_vec = veorq_u64   (acc_vec, shifted);
+            uint64x2_t shifted  = vshrq_n_u64(acc_vec, 47);
+            uint64x2_t data_vec = veorq_u64(acc_vec, shifted);
 
             /* xacc[i] ^= xsecret[i]; */
-            uint8x16_t key_vec  = vld1q_u8    (xsecret + (i * 16));
-            uint64x2_t data_key = veorq_u64   (data_vec, vreinterpretq_u64_u8(key_vec));
-
+            uint64x2_t key_vec  = XXH_vld1q_u64(xsecret + (i * 16));
+            uint64x2_t data_key = veorq_u64(data_vec, key_vec);
             /* xacc[i] *= XXH_PRIME32_1 */
-            uint32x2_t data_key_lo, data_key_hi;
-            /* data_key_lo = (uint32x2_t) (xacc[i] & 0xFFFFFFFF);
-             * data_key_hi = (uint32x2_t) (xacc[i] >> 32);
-             * xacc[i] = UNDEFINED; */
-            XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi);
-            {   /*
-                 * prod_hi = (data_key >> 32) * XXH_PRIME32_1;
-                 *
-                 * Avoid vmul_u32 + vshll_n_u32 since Clang 6 and 7 will
-                 * incorrectly "optimize" this:
-                 *   tmp     = vmul_u32(vmovn_u64(a), vmovn_u64(b));
-                 *   shifted = vshll_n_u32(tmp, 32);
-                 * to this:
-                 *   tmp     = "vmulq_u64"(a, b); // no such thing!
-                 *   shifted = vshlq_n_u64(tmp, 32);
-                 *
-                 * However, unlike SSE, Clang lacks a 64-bit multiply routine
-                 * for NEON, and it scalarizes two 64-bit multiplies instead.
-                 *
-                 * vmull_u32 has the same timing as vmul_u32, and it avoids
-                 * this bug completely.
-                 * See https://bugs.llvm.org/show_bug.cgi?id=39967
-                 */
-                uint64x2_t prod_hi = vmull_u32 (data_key_hi, prime);
-                /* xacc[i] = prod_hi << 32; */
-                xacc[i] = vshlq_n_u64(prod_hi, 32);
-                /* xacc[i] += (prod_hi & 0xFFFFFFFF) * XXH_PRIME32_1; */
-                xacc[i] = vmlal_u32(xacc[i], data_key_lo, prime);
-            }
-    }   }
+#ifdef __wasm_simd128__
+            /* SIMD128 has multiply by u64x2, use it instead of expanding and scalarizing */
+            xacc[i] = data_key * XXH_PRIME32_1;
+#else
+            /*
+             * Expanded version with portable NEON intrinsics
+             *
+             *    lo(x) * lo(y) + (hi(x) * lo(y) << 32)
+             *
+             * prod_hi = hi(data_key) * lo(prime) << 32
+             *
+             * Since we only need 32 bits of this multiply a trick can be used, reinterpreting the vector
+             * as a uint32x4_t and multiplying by { 0, prime, 0, prime } to cancel out the unwanted bits
+             * and avoid the shift.
+             */
+            uint32x4_t prod_hi = vmulq_u32 (vreinterpretq_u32_u64(data_key), kPrimeHi);
+            /* Extract low bits for vmlal_u32  */
+            uint32x2_t data_key_lo = vmovn_u64(data_key);
+            /* xacc[i] = prod_hi + lo(data_key) * XXH_PRIME32_1; */
+            xacc[i] = vmlal_u32(vreinterpretq_u64_u32(prod_hi), data_key_lo, kPrimeLo);
+#endif
+        }
+    }
 }
-
 #endif
 
 #if (XXH_VECTOR == XXH_VSX)
@@ -4129,23 +5110,23 @@ XXH3_accumulate_512_vsx(  void* XXH_RESTRICT acc,
                     const void* XXH_RESTRICT secret)
 {
     /* presumed aligned */
-    unsigned long long* const xacc = (unsigned long long*) acc;
-    xxh_u64x2 const* const xinput   = (xxh_u64x2 const*) input;   /* no alignment restriction */
-    xxh_u64x2 const* const xsecret  = (xxh_u64x2 const*) secret;    /* no alignment restriction */
+    xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc;
+    xxh_u8 const* const xinput   = (xxh_u8 const*) input;   /* no alignment restriction */
+    xxh_u8 const* const xsecret  = (xxh_u8 const*) secret;    /* no alignment restriction */
     xxh_u64x2 const v32 = { 32, 32 };
     size_t i;
     for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) {
         /* data_vec = xinput[i]; */
-        xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + i);
+        xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + 16*i);
         /* key_vec = xsecret[i]; */
-        xxh_u64x2 const key_vec  = XXH_vec_loadu(xsecret + i);
+        xxh_u64x2 const key_vec  = XXH_vec_loadu(xsecret + 16*i);
         xxh_u64x2 const data_key = data_vec ^ key_vec;
         /* shuffled = (data_key << 32) | (data_key >> 32); */
         xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32);
         /* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */
         xxh_u64x2 const product  = XXH_vec_mulo((xxh_u32x4)data_key, shuffled);
         /* acc_vec = xacc[i]; */
-        xxh_u64x2 acc_vec        = vec_xl(0, xacc + 2 * i);
+        xxh_u64x2 acc_vec        = xacc[i];
         acc_vec += product;
 
         /* swap high and low halves */
@@ -4154,18 +5135,18 @@ XXH3_accumulate_512_vsx(  void* XXH_RESTRICT acc,
 #else
         acc_vec += vec_xxpermdi(data_vec, data_vec, 2);
 #endif
-        /* xacc[i] = acc_vec; */
-        vec_xst(acc_vec, 0, xacc + 2 * i);
+        xacc[i] = acc_vec;
     }
 }
+XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(vsx)
 
 XXH_FORCE_INLINE void
 XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
 {
     XXH_ASSERT((((size_t)acc) & 15) == 0);
 
-    {         xxh_u64x2* const xacc    =       (xxh_u64x2*) acc;
-        const xxh_u64x2* const xsecret = (const xxh_u64x2*) secret;
+    {   xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc;
+        const xxh_u8* const xsecret = (const xxh_u8*) secret;
         /* constants */
         xxh_u64x2 const v32  = { 32, 32 };
         xxh_u64x2 const v47 = { 47, 47 };
@@ -4177,7 +5158,7 @@ XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
             xxh_u64x2 const data_vec = acc_vec ^ (acc_vec >> v47);
 
             /* xacc[i] ^= xsecret[i]; */
-            xxh_u64x2 const key_vec  = XXH_vec_loadu(xsecret + i);
+            xxh_u64x2 const key_vec  = XXH_vec_loadu(xsecret + 16*i);
             xxh_u64x2 const data_key = data_vec ^ key_vec;
 
             /* xacc[i] *= XXH_PRIME32_1 */
@@ -4191,40 +5172,233 @@ XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
 
 #endif
 
+#if (XXH_VECTOR == XXH_SVE)
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_sve( void* XXH_RESTRICT acc,
+                   const void* XXH_RESTRICT input,
+                   const void* XXH_RESTRICT secret)
+{
+    uint64_t *xacc = (uint64_t *)acc;
+    const uint64_t *xinput = (const uint64_t *)(const void *)input;
+    const uint64_t *xsecret = (const uint64_t *)(const void *)secret;
+    svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1);
+    uint64_t element_count = svcntd();
+    if (element_count >= 8) {
+        svbool_t mask = svptrue_pat_b64(SV_VL8);
+        svuint64_t vacc = svld1_u64(mask, xacc);
+        ACCRND(vacc, 0);
+        svst1_u64(mask, xacc, vacc);
+    } else if (element_count == 2) {   /* sve128 */
+        svbool_t mask = svptrue_pat_b64(SV_VL2);
+        svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+        svuint64_t acc1 = svld1_u64(mask, xacc + 2);
+        svuint64_t acc2 = svld1_u64(mask, xacc + 4);
+        svuint64_t acc3 = svld1_u64(mask, xacc + 6);
+        ACCRND(acc0, 0);
+        ACCRND(acc1, 2);
+        ACCRND(acc2, 4);
+        ACCRND(acc3, 6);
+        svst1_u64(mask, xacc + 0, acc0);
+        svst1_u64(mask, xacc + 2, acc1);
+        svst1_u64(mask, xacc + 4, acc2);
+        svst1_u64(mask, xacc + 6, acc3);
+    } else {
+        svbool_t mask = svptrue_pat_b64(SV_VL4);
+        svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+        svuint64_t acc1 = svld1_u64(mask, xacc + 4);
+        ACCRND(acc0, 0);
+        ACCRND(acc1, 4);
+        svst1_u64(mask, xacc + 0, acc0);
+        svst1_u64(mask, xacc + 4, acc1);
+    }
+}
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_sve(xxh_u64* XXH_RESTRICT acc,
+               const xxh_u8* XXH_RESTRICT input,
+               const xxh_u8* XXH_RESTRICT secret,
+               size_t nbStripes)
+{
+    if (nbStripes != 0) {
+        uint64_t *xacc = (uint64_t *)acc;
+        const uint64_t *xinput = (const uint64_t *)(const void *)input;
+        const uint64_t *xsecret = (const uint64_t *)(const void *)secret;
+        svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1);
+        uint64_t element_count = svcntd();
+        if (element_count >= 8) {
+            svbool_t mask = svptrue_pat_b64(SV_VL8);
+            svuint64_t vacc = svld1_u64(mask, xacc + 0);
+            do {
+                /* svprfd(svbool_t, void *, enum svfprop); */
+                svprfd(mask, xinput + 128, SV_PLDL1STRM);
+                ACCRND(vacc, 0);
+                xinput += 8;
+                xsecret += 1;
+                nbStripes--;
+           } while (nbStripes != 0);
+
+           svst1_u64(mask, xacc + 0, vacc);
+        } else if (element_count == 2) { /* sve128 */
+            svbool_t mask = svptrue_pat_b64(SV_VL2);
+            svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+            svuint64_t acc1 = svld1_u64(mask, xacc + 2);
+            svuint64_t acc2 = svld1_u64(mask, xacc + 4);
+            svuint64_t acc3 = svld1_u64(mask, xacc + 6);
+            do {
+                svprfd(mask, xinput + 128, SV_PLDL1STRM);
+                ACCRND(acc0, 0);
+                ACCRND(acc1, 2);
+                ACCRND(acc2, 4);
+                ACCRND(acc3, 6);
+                xinput += 8;
+                xsecret += 1;
+                nbStripes--;
+           } while (nbStripes != 0);
+
+           svst1_u64(mask, xacc + 0, acc0);
+           svst1_u64(mask, xacc + 2, acc1);
+           svst1_u64(mask, xacc + 4, acc2);
+           svst1_u64(mask, xacc + 6, acc3);
+        } else {
+            svbool_t mask = svptrue_pat_b64(SV_VL4);
+            svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+            svuint64_t acc1 = svld1_u64(mask, xacc + 4);
+            do {
+                svprfd(mask, xinput + 128, SV_PLDL1STRM);
+                ACCRND(acc0, 0);
+                ACCRND(acc1, 4);
+                xinput += 8;
+                xsecret += 1;
+                nbStripes--;
+           } while (nbStripes != 0);
+
+           svst1_u64(mask, xacc + 0, acc0);
+           svst1_u64(mask, xacc + 4, acc1);
+       }
+    }
+}
+
+#endif
+
 /* scalar variants - universal */
 
+#if defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__))
+/*
+ * In XXH3_scalarRound(), GCC and Clang have a similar codegen issue, where they
+ * emit an excess mask and a full 64-bit multiply-add (MADD X-form).
+ *
+ * While this might not seem like much, as AArch64 is a 64-bit architecture, only
+ * big Cortex designs have a full 64-bit multiplier.
+ *
+ * On the little cores, the smaller 32-bit multiplier is used, and full 64-bit
+ * multiplies expand to 2-3 multiplies in microcode. This has a major penalty
+ * of up to 4 latency cycles and 2 stall cycles in the multiply pipeline.
+ *
+ * Thankfully, AArch64 still provides the 32-bit long multiply-add (UMADDL) which does
+ * not have this penalty and does the mask automatically.
+ */
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc)
+{
+    xxh_u64 ret;
+    /* note: %x = 64-bit register, %w = 32-bit register */
+    __asm__("umaddl %x0, %w1, %w2, %x3" : "=r" (ret) : "r" (lhs), "r" (rhs), "r" (acc));
+    return ret;
+}
+#else
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc)
+{
+    return XXH_mult32to64((xxh_u32)lhs, (xxh_u32)rhs) + acc;
+}
+#endif
+
+/*!
+ * @internal
+ * @brief Scalar round for @ref XXH3_accumulate_512_scalar().
+ *
+ * This is extracted to its own function because the NEON path uses a combination
+ * of NEON and scalar.
+ */
+XXH_FORCE_INLINE void
+XXH3_scalarRound(void* XXH_RESTRICT acc,
+                 void const* XXH_RESTRICT input,
+                 void const* XXH_RESTRICT secret,
+                 size_t lane)
+{
+    xxh_u64* xacc = (xxh_u64*) acc;
+    xxh_u8 const* xinput  = (xxh_u8 const*) input;
+    xxh_u8 const* xsecret = (xxh_u8 const*) secret;
+    XXH_ASSERT(lane < XXH_ACC_NB);
+    XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0);
+    {
+        xxh_u64 const data_val = XXH_readLE64(xinput + lane * 8);
+        xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + lane * 8);
+        xacc[lane ^ 1] += data_val; /* swap adjacent lanes */
+        xacc[lane] = XXH_mult32to64_add64(data_key /* & 0xFFFFFFFF */, data_key >> 32, xacc[lane]);
+    }
+}
+
+/*!
+ * @internal
+ * @brief Processes a 64 byte block of data using the scalar path.
+ */
 XXH_FORCE_INLINE void
 XXH3_accumulate_512_scalar(void* XXH_RESTRICT acc,
                      const void* XXH_RESTRICT input,
                      const void* XXH_RESTRICT secret)
 {
-    xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
-    const xxh_u8* const xinput  = (const xxh_u8*) input;  /* no alignment restriction */
-    const xxh_u8* const xsecret = (const xxh_u8*) secret;   /* no alignment restriction */
     size_t i;
-    XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0);
+    /* ARM GCC refuses to unroll this loop, resulting in a 24% slowdown on ARMv6. */
+#if defined(__GNUC__) && !defined(__clang__) \
+  && (defined(__arm__) || defined(__thumb2__)) \
+  && defined(__ARM_FEATURE_UNALIGNED) /* no unaligned access just wastes bytes */ \
+  && XXH_SIZE_OPT <= 0
+#  pragma GCC unroll 8
+#endif
     for (i=0; i < XXH_ACC_NB; i++) {
-        xxh_u64 const data_val = XXH_readLE64(xinput + 8*i);
-        xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + i*8);
-        xacc[i ^ 1] += data_val; /* swap adjacent lanes */
-        xacc[i] += XXH_mult32to64(data_key & 0xFFFFFFFF, data_key >> 32);
+        XXH3_scalarRound(acc, input, secret, i);
     }
 }
+XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(scalar)
 
+/*!
+ * @internal
+ * @brief Scalar scramble step for @ref XXH3_scrambleAcc_scalar().
+ *
+ * This is extracted to its own function because the NEON path uses a combination
+ * of NEON and scalar.
+ */
 XXH_FORCE_INLINE void
-XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+XXH3_scalarScrambleRound(void* XXH_RESTRICT acc,
+                         void const* XXH_RESTRICT secret,
+                         size_t lane)
 {
     xxh_u64* const xacc = (xxh_u64*) acc;   /* presumed aligned */
     const xxh_u8* const xsecret = (const xxh_u8*) secret;   /* no alignment restriction */
-    size_t i;
     XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0);
-    for (i=0; i < XXH_ACC_NB; i++) {
-        xxh_u64 const key64 = XXH_readLE64(xsecret + 8*i);
-        xxh_u64 acc64 = xacc[i];
+    XXH_ASSERT(lane < XXH_ACC_NB);
+    {
+        xxh_u64 const key64 = XXH_readLE64(xsecret + lane * 8);
+        xxh_u64 acc64 = xacc[lane];
         acc64 = XXH_xorshift64(acc64, 47);
         acc64 ^= key64;
         acc64 *= XXH_PRIME32_1;
-        xacc[i] = acc64;
+        xacc[lane] = acc64;
+    }
+}
+
+/*!
+ * @internal
+ * @brief Scrambles the accumulators after a large chunk has been read
+ */
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+    size_t i;
+    for (i=0; i < XXH_ACC_NB; i++) {
+        XXH3_scalarScrambleRound(acc, secret, i);
     }
 }
 
@@ -4239,15 +5413,16 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
     const xxh_u8* kSecretPtr = XXH3_kSecret;
     XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
 
-#if defined(__clang__) && defined(__aarch64__)
+#if defined(__GNUC__) && defined(__aarch64__)
     /*
      * UGLY HACK:
-     * Clang generates a bunch of MOV/MOVK pairs for aarch64, and they are
+     * GCC and Clang generate a bunch of MOV/MOVK pairs for aarch64, and they are
      * placed sequentially, in order, at the top of the unrolled loop.
      *
      * While MOVK is great for generating constants (2 cycles for a 64-bit
-     * constant compared to 4 cycles for LDR), long MOVK chains stall the
-     * integer pipelines:
+     * constant compared to 4 cycles for LDR), it fights for bandwidth with
+     * the arithmetic instructions.
+     *
      *   I   L   S
      * MOVK
      * MOVK
@@ -4256,7 +5431,7 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
      * ADD
      * SUB      STR
      *          STR
-     * By forcing loads from memory (as the asm line causes Clang to assume
+     * By forcing loads from memory (as the asm line causes the compiler to assume
      * that XXH3_kSecretPtr has been changed), the pipelines are used more
      * efficiently:
      *   I   L   S
@@ -4264,23 +5439,20 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
      *  ADD LDR
      *  SUB     STR
      *          STR
+     *
+     * See XXH3_NEON_LANES for details on the pipsline.
+     *
      * XXH3_64bits_withSeed, len == 256, Snapdragon 835
      *   without hack: 2654.4 MB/s
      *   with hack:    3202.9 MB/s
      */
     XXH_COMPILER_GUARD(kSecretPtr);
 #endif
-    /*
-     * Note: in debug mode, this overrides the asm optimization
-     * and Clang will emit MOVK chains again.
-     */
-    XXH_ASSERT(kSecretPtr == XXH3_kSecret);
-
     {   int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16;
         int i;
         for (i=0; i < nbRounds; i++) {
             /*
-             * The asm hack causes Clang to assume that kSecretPtr aliases with
+             * The asm hack causes the compiler to assume that kSecretPtr aliases with
              * customSecret, and on aarch64, this prevented LDP from merging two
              * loads together for free. Putting the loads together before the stores
              * properly generates LDP.
@@ -4293,7 +5465,7 @@ XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
 }
 
 
-typedef void (*XXH3_f_accumulate_512)(void* XXH_RESTRICT, const void*, const void*);
+typedef void (*XXH3_f_accumulate)(xxh_u64* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, size_t);
 typedef void (*XXH3_f_scrambleAcc)(void* XXH_RESTRICT, const void*);
 typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64);
 
@@ -4301,82 +5473,63 @@ typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64);
 #if (XXH_VECTOR == XXH_AVX512)
 
 #define XXH3_accumulate_512 XXH3_accumulate_512_avx512
+#define XXH3_accumulate     XXH3_accumulate_avx512
 #define XXH3_scrambleAcc    XXH3_scrambleAcc_avx512
 #define XXH3_initCustomSecret XXH3_initCustomSecret_avx512
 
 #elif (XXH_VECTOR == XXH_AVX2)
 
 #define XXH3_accumulate_512 XXH3_accumulate_512_avx2
+#define XXH3_accumulate     XXH3_accumulate_avx2
 #define XXH3_scrambleAcc    XXH3_scrambleAcc_avx2
 #define XXH3_initCustomSecret XXH3_initCustomSecret_avx2
 
 #elif (XXH_VECTOR == XXH_SSE2)
 
 #define XXH3_accumulate_512 XXH3_accumulate_512_sse2
+#define XXH3_accumulate     XXH3_accumulate_sse2
 #define XXH3_scrambleAcc    XXH3_scrambleAcc_sse2
 #define XXH3_initCustomSecret XXH3_initCustomSecret_sse2
 
 #elif (XXH_VECTOR == XXH_NEON)
 
 #define XXH3_accumulate_512 XXH3_accumulate_512_neon
+#define XXH3_accumulate     XXH3_accumulate_neon
 #define XXH3_scrambleAcc    XXH3_scrambleAcc_neon
 #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
 
 #elif (XXH_VECTOR == XXH_VSX)
 
 #define XXH3_accumulate_512 XXH3_accumulate_512_vsx
+#define XXH3_accumulate     XXH3_accumulate_vsx
 #define XXH3_scrambleAcc    XXH3_scrambleAcc_vsx
 #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
 
+#elif (XXH_VECTOR == XXH_SVE)
+#define XXH3_accumulate_512 XXH3_accumulate_512_sve
+#define XXH3_accumulate     XXH3_accumulate_sve
+#define XXH3_scrambleAcc    XXH3_scrambleAcc_scalar
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
 #else /* scalar */
 
 #define XXH3_accumulate_512 XXH3_accumulate_512_scalar
+#define XXH3_accumulate     XXH3_accumulate_scalar
 #define XXH3_scrambleAcc    XXH3_scrambleAcc_scalar
 #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
 
 #endif
 
-
-
-#ifndef XXH_PREFETCH_DIST
-#  ifdef __clang__
-#    define XXH_PREFETCH_DIST 320
-#  else
-#    if (XXH_VECTOR == XXH_AVX512)
-#      define XXH_PREFETCH_DIST 512
-#    else
-#      define XXH_PREFETCH_DIST 384
-#    endif
-#  endif  /* __clang__ */
-#endif  /* XXH_PREFETCH_DIST */
-
-/*
- * XXH3_accumulate()
- * Loops over XXH3_accumulate_512().
- * Assumption: nbStripes will not overflow the secret size
- */
-XXH_FORCE_INLINE void
-XXH3_accumulate(     xxh_u64* XXH_RESTRICT acc,
-                const xxh_u8* XXH_RESTRICT input,
-                const xxh_u8* XXH_RESTRICT secret,
-                      size_t nbStripes,
-                      XXH3_f_accumulate_512 f_acc512)
-{
-    size_t n;
-    for (n = 0; n < nbStripes; n++ ) {
-        const xxh_u8* const in = input + n*XXH_STRIPE_LEN;
-        XXH_PREFETCH(in + XXH_PREFETCH_DIST);
-        f_acc512(acc,
-                 in,
-                 secret + n*XXH_SECRET_CONSUME_RATE);
-    }
-}
+#if XXH_SIZE_OPT >= 1 /* don't do SIMD for initialization */
+#  undef XXH3_initCustomSecret
+#  define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+#endif
 
 XXH_FORCE_INLINE void
 XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc,
                       const xxh_u8* XXH_RESTRICT input, size_t len,
                       const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
-                            XXH3_f_accumulate_512 f_acc512,
+                            XXH3_f_accumulate f_acc,
                             XXH3_f_scrambleAcc f_scramble)
 {
     size_t const nbStripesPerBlock = (secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE;
@@ -4388,7 +5541,7 @@ XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc,
     XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
 
     for (n = 0; n < nb_blocks; n++) {
-        XXH3_accumulate(acc, input + n*block_len, secret, nbStripesPerBlock, f_acc512);
+        f_acc(acc, input + n*block_len, secret, nbStripesPerBlock);
         f_scramble(acc, secret + secretSize - XXH_STRIPE_LEN);
     }
 
@@ -4396,12 +5549,12 @@ XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc,
     XXH_ASSERT(len > XXH_STRIPE_LEN);
     {   size_t const nbStripes = ((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN;
         XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE));
-        XXH3_accumulate(acc, input + nb_blocks*block_len, secret, nbStripes, f_acc512);
+        f_acc(acc, input + nb_blocks*block_len, secret, nbStripes);
 
         /* last stripe */
         {   const xxh_u8* const p = input + len - XXH_STRIPE_LEN;
 #define XXH_SECRET_LASTACC_START 7  /* not aligned on 8, last secret is different from acc & scrambler */
-            f_acc512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START);
+            XXH3_accumulate_512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START);
     }   }
 }
 
@@ -4446,12 +5599,12 @@ XXH3_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secre
 XXH_FORCE_INLINE XXH64_hash_t
 XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len,
                            const void* XXH_RESTRICT secret, size_t secretSize,
-                           XXH3_f_accumulate_512 f_acc512,
+                           XXH3_f_accumulate f_acc,
                            XXH3_f_scrambleAcc f_scramble)
 {
     XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
 
-    XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc512, f_scramble);
+    XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc, f_scramble);
 
     /* converge into final hash */
     XXH_STATIC_ASSERT(sizeof(acc) == 64);
@@ -4465,13 +5618,15 @@ XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len,
  * It's important for performance to transmit secret's size (when it's static)
  * so that the compiler can properly optimize the vectorized loop.
  * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set.
+ * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE
+ * breaks -Og, this is XXH_NO_INLINE.
  */
-XXH_FORCE_INLINE XXH64_hash_t
+XXH3_WITH_SECRET_INLINE XXH64_hash_t
 XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len,
                              XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
 {
     (void)seed64;
-    return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate_512, XXH3_scrambleAcc);
+    return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate, XXH3_scrambleAcc);
 }
 
 /*
@@ -4480,12 +5635,12 @@ XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len,
  * Note that inside this no_inline function, we do inline the internal loop,
  * and provide a statically defined secret size to allow optimization of vector loop.
  */
-XXH_NO_INLINE XXH64_hash_t
+XXH_NO_INLINE XXH_PUREF XXH64_hash_t
 XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len,
                           XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
 {
     (void)seed64; (void)secret; (void)secretLen;
-    return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512, XXH3_scrambleAcc);
+    return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate, XXH3_scrambleAcc);
 }
 
 /*
@@ -4502,18 +5657,20 @@ XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len,
 XXH_FORCE_INLINE XXH64_hash_t
 XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len,
                                     XXH64_hash_t seed,
-                                    XXH3_f_accumulate_512 f_acc512,
+                                    XXH3_f_accumulate f_acc,
                                     XXH3_f_scrambleAcc f_scramble,
                                     XXH3_f_initCustomSecret f_initSec)
 {
+#if XXH_SIZE_OPT <= 0
     if (seed == 0)
         return XXH3_hashLong_64b_internal(input, len,
                                           XXH3_kSecret, sizeof(XXH3_kSecret),
-                                          f_acc512, f_scramble);
+                                          f_acc, f_scramble);
+#endif
     {   XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
         f_initSec(secret, seed);
         return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret),
-                                          f_acc512, f_scramble);
+                                          f_acc, f_scramble);
     }
 }
 
@@ -4521,12 +5678,12 @@ XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len,
  * It's important for performance that XXH3_hashLong is not inlined.
  */
 XXH_NO_INLINE XXH64_hash_t
-XXH3_hashLong_64b_withSeed(const void* input, size_t len,
-                           XXH64_hash_t seed, const xxh_u8* secret, size_t secretLen)
+XXH3_hashLong_64b_withSeed(const void* XXH_RESTRICT input, size_t len,
+                           XXH64_hash_t seed, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
 {
     (void)secret; (void)secretLen;
     return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
-                XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret);
+                XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret);
 }
 
 
@@ -4558,37 +5715,37 @@ XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len,
 
 /* ===   Public entry point   === */
 
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* input, size_t len)
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length)
 {
-    return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default);
+    return XXH3_64bits_internal(input, length, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH64_hash_t
-XXH3_64bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
+XXH3_64bits_withSecret(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize)
 {
-    return XXH3_64bits_internal(input, len, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
+    return XXH3_64bits_internal(input, length, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH64_hash_t
-XXH3_64bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
+XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed)
 {
-    return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
+    return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
 }
 
 XXH_PUBLIC_API XXH64_hash_t
-XXH3_64bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed)
+XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed)
 {
-    if (len <= XXH3_MIDSIZE_MAX)
-        return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
-    return XXH3_hashLong_64b_withSecret(input, len, seed, (const xxh_u8*)secret, secretSize);
+    if (length <= XXH3_MIDSIZE_MAX)
+        return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+    return XXH3_hashLong_64b_withSecret(input, length, seed, (const xxh_u8*)secret, secretSize);
 }
 
 
 /* ===   XXH3 streaming   === */
-
+#ifndef XXH_NO_STREAM
 /*
  * Malloc's a pointer that is always aligned to align.
  *
@@ -4612,7 +5769,7 @@ XXH3_64bits_withSecretandSeed(const void* input, size_t len, const void* secret,
  *
  * Align must be a power of 2 and 8 <= align <= 128.
  */
-static void* XXH_alignedMalloc(size_t s, size_t align)
+static XXH_MALLOCF void* XXH_alignedMalloc(size_t s, size_t align)
 {
     XXH_ASSERT(align <= 128 && align >= 8); /* range check */
     XXH_ASSERT((align & (align-1)) == 0);   /* power of 2 */
@@ -4654,7 +5811,13 @@ static void XXH_alignedFree(void* p)
         XXH_free(base);
     }
 }
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
+/*!
+ * @brief Allocate an @ref XXH3_state_t.
+ *
+ * Must be freed with XXH3_freeState().
+ * @return An allocated XXH3_state_t on success, `NULL` on failure.
+ */
 XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void)
 {
     XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64);
@@ -4663,16 +5826,23 @@ XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void)
     return state;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
+/*!
+ * @brief Frees an @ref XXH3_state_t.
+ *
+ * Must be allocated with XXH3_createState().
+ * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState().
+ * @return XXH_OK.
+ */
 XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr)
 {
     XXH_alignedFree(statePtr);
     return XXH_OK;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API void
-XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state)
+XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state)
 {
     XXH_memcpy(dst_state, src_state, sizeof(*dst_state));
 }
@@ -4704,18 +5874,18 @@ XXH3_reset_internal(XXH3_state_t* statePtr,
     statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_64bits_reset(XXH3_state_t* statePtr)
+XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr)
 {
     if (statePtr == NULL) return XXH_ERROR;
     XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
     return XXH_OK;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize)
+XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize)
 {
     if (statePtr == NULL) return XXH_ERROR;
     XXH3_reset_internal(statePtr, 0, secret, secretSize);
@@ -4724,9 +5894,9 @@ XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t
     return XXH_OK;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
+XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed)
 {
     if (statePtr == NULL) return XXH_ERROR;
     if (seed==0) return XXH3_64bits_reset(statePtr);
@@ -4736,9 +5906,9 @@ XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
     return XXH_OK;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed64)
+XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64)
 {
     if (statePtr == NULL) return XXH_ERROR;
     if (secret == NULL) return XXH_ERROR;
@@ -4748,35 +5918,61 @@ XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret,
     return XXH_OK;
 }
 
-/* Note : when XXH3_consumeStripes() is invoked,
- * there must be a guarantee that at least one more byte must be consumed from input
- * so that the function can blindly consume all stripes using the "normal" secret segment */
-XXH_FORCE_INLINE void
+/*!
+ * @internal
+ * @brief Processes a large input for XXH3_update() and XXH3_digest_long().
+ *
+ * Unlike XXH3_hashLong_internal_loop(), this can process data that overlaps a block.
+ *
+ * @param acc                Pointer to the 8 accumulator lanes
+ * @param nbStripesSoFarPtr  In/out pointer to the number of leftover stripes in the block*
+ * @param nbStripesPerBlock  Number of stripes in a block
+ * @param input              Input pointer
+ * @param nbStripes          Number of stripes to process
+ * @param secret             Secret pointer
+ * @param secretLimit        Offset of the last block in @p secret
+ * @param f_acc              Pointer to an XXH3_accumulate implementation
+ * @param f_scramble         Pointer to an XXH3_scrambleAcc implementation
+ * @return                   Pointer past the end of @p input after processing
+ */
+XXH_FORCE_INLINE const xxh_u8 *
 XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc,
                     size_t* XXH_RESTRICT nbStripesSoFarPtr, size_t nbStripesPerBlock,
                     const xxh_u8* XXH_RESTRICT input, size_t nbStripes,
                     const xxh_u8* XXH_RESTRICT secret, size_t secretLimit,
-                    XXH3_f_accumulate_512 f_acc512,
+                    XXH3_f_accumulate f_acc,
                     XXH3_f_scrambleAcc f_scramble)
 {
-    XXH_ASSERT(nbStripes <= nbStripesPerBlock);  /* can handle max 1 scramble per invocation */
-    XXH_ASSERT(*nbStripesSoFarPtr < nbStripesPerBlock);
-    if (nbStripesPerBlock - *nbStripesSoFarPtr <= nbStripes) {
-        /* need a scrambling operation */
-        size_t const nbStripesToEndofBlock = nbStripesPerBlock - *nbStripesSoFarPtr;
-        size_t const nbStripesAfterBlock = nbStripes - nbStripesToEndofBlock;
-        XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripesToEndofBlock, f_acc512);
-        f_scramble(acc, secret + secretLimit);
-        XXH3_accumulate(acc, input + nbStripesToEndofBlock * XXH_STRIPE_LEN, secret, nbStripesAfterBlock, f_acc512);
-        *nbStripesSoFarPtr = nbStripesAfterBlock;
-    } else {
-        XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripes, f_acc512);
+    const xxh_u8* initialSecret = secret + *nbStripesSoFarPtr * XXH_SECRET_CONSUME_RATE;
+    /* Process full blocks */
+    if (nbStripes >= (nbStripesPerBlock - *nbStripesSoFarPtr)) {
+        /* Process the initial partial block... */
+        size_t nbStripesThisIter = nbStripesPerBlock - *nbStripesSoFarPtr;
+
+        do {
+            /* Accumulate and scramble */
+            f_acc(acc, input, initialSecret, nbStripesThisIter);
+            f_scramble(acc, secret + secretLimit);
+            input += nbStripesThisIter * XXH_STRIPE_LEN;
+            nbStripes -= nbStripesThisIter;
+            /* Then continue the loop with the full block size */
+            nbStripesThisIter = nbStripesPerBlock;
+            initialSecret = secret;
+        } while (nbStripes >= nbStripesPerBlock);
+        *nbStripesSoFarPtr = 0;
+    }
+    /* Process a partial block */
+    if (nbStripes > 0) {
+        f_acc(acc, input, initialSecret, nbStripes);
+        input += nbStripes * XXH_STRIPE_LEN;
         *nbStripesSoFarPtr += nbStripes;
     }
+    /* Return end pointer */
+    return input;
 }
 
 #ifndef XXH3_STREAM_USE_STACK
-# ifndef __clang__ /* clang doesn't need additional stack space */
+# if XXH_SIZE_OPT <= 0 && !defined(__clang__) /* clang doesn't need additional stack space */
 #   define XXH3_STREAM_USE_STACK 1
 # endif
 #endif
@@ -4786,7 +5982,7 @@ XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc,
 XXH_FORCE_INLINE XXH_errorcode
 XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
             const xxh_u8* XXH_RESTRICT input, size_t len,
-            XXH3_f_accumulate_512 f_acc512,
+            XXH3_f_accumulate f_acc,
             XXH3_f_scrambleAcc f_scramble)
 {
     if (input==NULL) {
@@ -4802,7 +5998,8 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
          * when operating accumulators directly into state.
          * Operating into stack space seems to enable proper optimization.
          * clang, on the other hand, doesn't seem to need this trick */
-        XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; memcpy(acc, state->acc, sizeof(acc));
+        XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8];
+        XXH_memcpy(acc, state->acc, sizeof(acc));
 #else
         xxh_u64* XXH_RESTRICT const acc = state->acc;
 #endif
@@ -4810,7 +6007,7 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
         XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE);
 
         /* small input : just fill in tmp buffer */
-        if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) {
+        if (len <= XXH3_INTERNALBUFFER_SIZE - state->bufferedSize) {
             XXH_memcpy(state->buffer + state->bufferedSize, input, len);
             state->bufferedSize += (XXH32_hash_t)len;
             return XXH_OK;
@@ -4832,57 +6029,20 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
                                &state->nbStripesSoFar, state->nbStripesPerBlock,
                                 state->buffer, XXH3_INTERNALBUFFER_STRIPES,
                                 secret, state->secretLimit,
-                                f_acc512, f_scramble);
+                                f_acc, f_scramble);
             state->bufferedSize = 0;
         }
         XXH_ASSERT(input < bEnd);
-
-        /* large input to consume : ingest per full block */
-        if ((size_t)(bEnd - input) > state->nbStripesPerBlock * XXH_STRIPE_LEN) {
+        if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) {
             size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN;
-            XXH_ASSERT(state->nbStripesPerBlock >= state->nbStripesSoFar);
-            /* join to current block's end */
-            {   size_t const nbStripesToEnd = state->nbStripesPerBlock - state->nbStripesSoFar;
-                XXH_ASSERT(nbStripes <= nbStripes);
-                XXH3_accumulate(acc, input, secret + state->nbStripesSoFar * XXH_SECRET_CONSUME_RATE, nbStripesToEnd, f_acc512);
-                f_scramble(acc, secret + state->secretLimit);
-                state->nbStripesSoFar = 0;
-                input += nbStripesToEnd * XXH_STRIPE_LEN;
-                nbStripes -= nbStripesToEnd;
-            }
-            /* consume per entire blocks */
-            while(nbStripes >= state->nbStripesPerBlock) {
-                XXH3_accumulate(acc, input, secret, state->nbStripesPerBlock, f_acc512);
-                f_scramble(acc, secret + state->secretLimit);
-                input += state->nbStripesPerBlock * XXH_STRIPE_LEN;
-                nbStripes -= state->nbStripesPerBlock;
-            }
-            /* consume last partial block */
-            XXH3_accumulate(acc, input, secret, nbStripes, f_acc512);
-            input += nbStripes * XXH_STRIPE_LEN;
-            XXH_ASSERT(input < bEnd);  /* at least some bytes left */
-            state->nbStripesSoFar = nbStripes;
-            /* buffer predecessor of last partial stripe */
-            XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
-            XXH_ASSERT(bEnd - input <= XXH_STRIPE_LEN);
-        } else {
-            /* content to consume <= block size */
-            /* Consume input by a multiple of internal buffer size */
-            if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) {
-                const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE;
-                do {
-                    XXH3_consumeStripes(acc,
+            input = XXH3_consumeStripes(acc,
                                        &state->nbStripesSoFar, state->nbStripesPerBlock,
-                                        input, XXH3_INTERNALBUFFER_STRIPES,
-                                        secret, state->secretLimit,
-                                        f_acc512, f_scramble);
-                    input += XXH3_INTERNALBUFFER_SIZE;
-                } while (input<limit);
-                /* buffer predecessor of last partial stripe */
-                XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
-            }
-        }
+                                       input, nbStripes,
+                                       secret, state->secretLimit,
+                                       f_acc, f_scramble);
+            XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
 
+        }
         /* Some remaining input (always) : buffer it */
         XXH_ASSERT(input < bEnd);
         XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE);
@@ -4891,19 +6051,19 @@ XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
         state->bufferedSize = (XXH32_hash_t)(bEnd-input);
 #if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
         /* save stack accumulators into state */
-        memcpy(state->acc, acc, sizeof(acc));
+        XXH_memcpy(state->acc, acc, sizeof(acc));
 #endif
     }
 
     return XXH_OK;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_64bits_update(XXH3_state_t* state, const void* input, size_t len)
+XXH3_64bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
 {
     return XXH3_update(state, (const xxh_u8*)input, len,
-                       XXH3_accumulate_512, XXH3_scrambleAcc);
+                       XXH3_accumulate, XXH3_scrambleAcc);
 }
 
 
@@ -4912,37 +6072,40 @@ XXH3_digest_long (XXH64_hash_t* acc,
                   const XXH3_state_t* state,
                   const unsigned char* secret)
 {
+    xxh_u8 lastStripe[XXH_STRIPE_LEN];
+    const xxh_u8* lastStripePtr;
+
     /*
      * Digest on a local copy. This way, the state remains unaltered, and it can
      * continue ingesting more input afterwards.
      */
     XXH_memcpy(acc, state->acc, sizeof(state->acc));
     if (state->bufferedSize >= XXH_STRIPE_LEN) {
+        /* Consume remaining stripes then point to remaining data in buffer */
         size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN;
         size_t nbStripesSoFar = state->nbStripesSoFar;
         XXH3_consumeStripes(acc,
                            &nbStripesSoFar, state->nbStripesPerBlock,
                             state->buffer, nbStripes,
                             secret, state->secretLimit,
-                            XXH3_accumulate_512, XXH3_scrambleAcc);
-        /* last stripe */
-        XXH3_accumulate_512(acc,
-                            state->buffer + state->bufferedSize - XXH_STRIPE_LEN,
-                            secret + state->secretLimit - XXH_SECRET_LASTACC_START);
+                            XXH3_accumulate, XXH3_scrambleAcc);
+        lastStripePtr = state->buffer + state->bufferedSize - XXH_STRIPE_LEN;
     } else {  /* bufferedSize < XXH_STRIPE_LEN */
-        xxh_u8 lastStripe[XXH_STRIPE_LEN];
+        /* Copy to temp buffer */
         size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize;
         XXH_ASSERT(state->bufferedSize > 0);  /* there is always some input buffered */
         XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
         XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize);
-        XXH3_accumulate_512(acc,
-                            lastStripe,
-                            secret + state->secretLimit - XXH_SECRET_LASTACC_START);
+        lastStripePtr = lastStripe;
     }
+    /* Last stripe */
+    XXH3_accumulate_512(acc,
+                        lastStripePtr,
+                        secret + state->secretLimit - XXH_SECRET_LASTACC_START);
 }
 
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state)
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* state)
 {
     const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
     if (state->totalLen > XXH3_MIDSIZE_MAX) {
@@ -4958,7 +6121,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state)
     return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen),
                                   secret, state->secretLimit + XXH_STRIPE_LEN);
 }
-
+#endif /* !XXH_NO_STREAM */
 
 
 /* ==========================================
@@ -4978,7 +6141,7 @@ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state)
  * fast for a _128-bit_ hash on 32-bit (it usually clears XXH64).
  */
 
-XXH_FORCE_INLINE XXH128_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
 XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     /* A doubled version of 1to3_64b with different constants. */
@@ -5007,7 +6170,7 @@ XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_
     }
 }
 
-XXH_FORCE_INLINE XXH128_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
 XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(input != NULL);
@@ -5027,14 +6190,14 @@ XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_
         m128.low64  ^= (m128.high64 >> 3);
 
         m128.low64   = XXH_xorshift64(m128.low64, 35);
-        m128.low64  *= 0x9FB21C651E98DF25ULL;
+        m128.low64  *= PRIME_MX2;
         m128.low64   = XXH_xorshift64(m128.low64, 28);
         m128.high64  = XXH3_avalanche(m128.high64);
         return m128;
     }
 }
 
-XXH_FORCE_INLINE XXH128_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
 XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(input != NULL);
@@ -5109,7 +6272,7 @@ XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64
 /*
  * Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN
  */
-XXH_FORCE_INLINE XXH128_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
 XXH3_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
 {
     XXH_ASSERT(len <= 16);
@@ -5140,7 +6303,7 @@ XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2,
 }
 
 
-XXH_FORCE_INLINE XXH128_hash_t
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
 XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
                       const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
                       XXH64_hash_t seed)
@@ -5151,6 +6314,16 @@ XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
     {   XXH128_hash_t acc;
         acc.low64 = len * XXH_PRIME64_1;
         acc.high64 = 0;
+
+#if XXH_SIZE_OPT >= 1
+        {
+            /* Smaller, but slightly slower. */
+            unsigned int i = (unsigned int)(len - 1) / 32;
+            do {
+                acc = XXH128_mix32B(acc, input+16*i, input+len-16*(i+1), secret+32*i, seed);
+            } while (i-- != 0);
+        }
+#else
         if (len > 32) {
             if (len > 64) {
                 if (len > 96) {
@@ -5161,6 +6334,7 @@ XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
             acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed);
         }
         acc = XXH128_mix32B(acc, input, input+len-16, secret, seed);
+#endif
         {   XXH128_hash_t h128;
             h128.low64  = acc.low64 + acc.high64;
             h128.high64 = (acc.low64    * XXH_PRIME64_1)
@@ -5173,7 +6347,7 @@ XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
     }
 }
 
-XXH_NO_INLINE XXH128_hash_t
+XXH_NO_INLINE XXH_PUREF XXH128_hash_t
 XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
                        const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
                        XXH64_hash_t seed)
@@ -5182,25 +6356,34 @@ XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
     XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
 
     {   XXH128_hash_t acc;
-        int const nbRounds = (int)len / 32;
-        int i;
+        unsigned i;
         acc.low64 = len * XXH_PRIME64_1;
         acc.high64 = 0;
-        for (i=0; i<4; i++) {
+        /*
+         *  We set as `i` as offset + 32. We do this so that unchanged
+         * `len` can be used as upper bound. This reaches a sweet spot
+         * where both x86 and aarch64 get simple agen and good codegen
+         * for the loop.
+         */
+        for (i = 32; i < 160; i += 32) {
             acc = XXH128_mix32B(acc,
-                                input  + (32 * i),
-                                input  + (32 * i) + 16,
-                                secret + (32 * i),
+                                input  + i - 32,
+                                input  + i - 16,
+                                secret + i - 32,
                                 seed);
         }
         acc.low64 = XXH3_avalanche(acc.low64);
         acc.high64 = XXH3_avalanche(acc.high64);
-        XXH_ASSERT(nbRounds >= 4);
-        for (i=4 ; i < nbRounds; i++) {
+        /*
+         * NB: `i <= len` will duplicate the last 32-bytes if
+         * len % 32 was zero. This is an unfortunate necessity to keep
+         * the hash result stable.
+         */
+        for (i=160; i <= len; i += 32) {
             acc = XXH128_mix32B(acc,
-                                input + (32 * i),
-                                input + (32 * i) + 16,
-                                secret + XXH3_MIDSIZE_STARTOFFSET + (32 * (i - 4)),
+                                input + i - 32,
+                                input + i - 16,
+                                secret + XXH3_MIDSIZE_STARTOFFSET + i - 160,
                                 seed);
         }
         /* last bytes */
@@ -5208,7 +6391,7 @@ XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
                             input + len - 16,
                             input + len - 32,
                             secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET - 16,
-                            0ULL - seed);
+                            (XXH64_hash_t)0 - seed);
 
         {   XXH128_hash_t h128;
             h128.low64  = acc.low64 + acc.high64;
@@ -5225,12 +6408,12 @@ XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
 XXH_FORCE_INLINE XXH128_hash_t
 XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len,
                             const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
-                            XXH3_f_accumulate_512 f_acc512,
+                            XXH3_f_accumulate f_acc,
                             XXH3_f_scrambleAcc f_scramble)
 {
     XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
 
-    XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc512, f_scramble);
+    XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc, f_scramble);
 
     /* converge into final hash */
     XXH_STATIC_ASSERT(sizeof(acc) == 64);
@@ -5248,47 +6431,50 @@ XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len,
 }
 
 /*
- * It's important for performance that XXH3_hashLong is not inlined.
+ * It's important for performance that XXH3_hashLong() is not inlined.
  */
-XXH_NO_INLINE XXH128_hash_t
+XXH_NO_INLINE XXH_PUREF XXH128_hash_t
 XXH3_hashLong_128b_default(const void* XXH_RESTRICT input, size_t len,
                            XXH64_hash_t seed64,
                            const void* XXH_RESTRICT secret, size_t secretLen)
 {
     (void)seed64; (void)secret; (void)secretLen;
     return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret),
-                                       XXH3_accumulate_512, XXH3_scrambleAcc);
+                                       XXH3_accumulate, XXH3_scrambleAcc);
 }
 
 /*
- * It's important for performance to pass @secretLen (when it's static)
+ * It's important for performance to pass @secretLen (when it's static)
  * to the compiler, so that it can properly optimize the vectorized loop.
+ *
+ * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE
+ * breaks -Og, this is XXH_NO_INLINE.
  */
-XXH_FORCE_INLINE XXH128_hash_t
+XXH3_WITH_SECRET_INLINE XXH128_hash_t
 XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len,
                               XXH64_hash_t seed64,
                               const void* XXH_RESTRICT secret, size_t secretLen)
 {
     (void)seed64;
     return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
-                                       XXH3_accumulate_512, XXH3_scrambleAcc);
+                                       XXH3_accumulate, XXH3_scrambleAcc);
 }
 
 XXH_FORCE_INLINE XXH128_hash_t
 XXH3_hashLong_128b_withSeed_internal(const void* XXH_RESTRICT input, size_t len,
                                 XXH64_hash_t seed64,
-                                XXH3_f_accumulate_512 f_acc512,
+                                XXH3_f_accumulate f_acc,
                                 XXH3_f_scrambleAcc f_scramble,
                                 XXH3_f_initCustomSecret f_initSec)
 {
     if (seed64 == 0)
         return XXH3_hashLong_128b_internal(input, len,
                                            XXH3_kSecret, sizeof(XXH3_kSecret),
-                                           f_acc512, f_scramble);
+                                           f_acc, f_scramble);
     {   XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
         f_initSec(secret, seed64);
         return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, sizeof(secret),
-                                           f_acc512, f_scramble);
+                                           f_acc, f_scramble);
     }
 }
 
@@ -5301,7 +6487,7 @@ XXH3_hashLong_128b_withSeed(const void* input, size_t len,
 {
     (void)secret; (void)secretLen;
     return XXH3_hashLong_128b_withSeed_internal(input, len, seed64,
-                XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret);
+                XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret);
 }
 
 typedef XXH128_hash_t (*XXH3_hashLong128_f)(const void* XXH_RESTRICT, size_t,
@@ -5331,94 +6517,93 @@ XXH3_128bits_internal(const void* input, size_t len,
 
 /* ===   Public XXH128 API   === */
 
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* input, size_t len)
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* input, size_t len)
 {
     return XXH3_128bits_internal(input, len, 0,
                                  XXH3_kSecret, sizeof(XXH3_kSecret),
                                  XXH3_hashLong_128b_default);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH128_hash_t
-XXH3_128bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
+XXH3_128bits_withSecret(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize)
 {
     return XXH3_128bits_internal(input, len, 0,
                                  (const xxh_u8*)secret, secretSize,
                                  XXH3_hashLong_128b_withSecret);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH128_hash_t
-XXH3_128bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
+XXH3_128bits_withSeed(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
 {
     return XXH3_128bits_internal(input, len, seed,
                                  XXH3_kSecret, sizeof(XXH3_kSecret),
                                  XXH3_hashLong_128b_withSeed);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH128_hash_t
-XXH3_128bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed)
+XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed)
 {
     if (len <= XXH3_MIDSIZE_MAX)
         return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
     return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH128_hash_t
-XXH128(const void* input, size_t len, XXH64_hash_t seed)
+XXH128(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
 {
     return XXH3_128bits_withSeed(input, len, seed);
 }
 
 
 /* ===   XXH3 128-bit streaming   === */
-
+#ifndef XXH_NO_STREAM
 /*
  * All initialization and update functions are identical to 64-bit streaming variant.
  * The only difference is the finalization routine.
  */
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_128bits_reset(XXH3_state_t* statePtr)
+XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr)
 {
     return XXH3_64bits_reset(statePtr);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize)
+XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize)
 {
     return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
+XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed)
 {
     return XXH3_64bits_reset_withSeed(statePtr, seed);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed)
+XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed)
 {
     return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed);
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_128bits_update(XXH3_state_t* state, const void* input, size_t len)
+XXH3_128bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
 {
-    return XXH3_update(state, (const xxh_u8*)input, len,
-                       XXH3_accumulate_512, XXH3_scrambleAcc);
+    return XXH3_64bits_update(state, input, len);
 }
 
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state)
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* state)
 {
     const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
     if (state->totalLen > XXH3_MIDSIZE_MAX) {
@@ -5442,13 +6627,13 @@ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state)
     return XXH3_128bits_withSecret(state->buffer, (size_t)(state->totalLen),
                                    secret, state->secretLimit + XXH_STRIPE_LEN);
 }
-
+#endif /* !XXH_NO_STREAM */
 /* 128-bit utility functions */
 
 #include <string.h>   /* memcmp, memcpy */
 
 /* return : 1 is equal, 0 if different */
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2)
 {
     /* note : XXH128_hash_t is compact, it has no padding byte */
@@ -5456,11 +6641,11 @@ XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2)
 }
 
 /* This prototype is compatible with stdlib's qsort().
- * return : >0 if *h128_1  > *h128_2
- *          <0 if *h128_1  < *h128_2
- *          =0 if *h128_1 == *h128_2  */
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2)
+ * @return : >0 if *h128_1  > *h128_2
+ *           <0 if *h128_1  < *h128_2
+ *           =0 if *h128_1 == *h128_2  */
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2)
 {
     XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1;
     XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2;
@@ -5472,9 +6657,9 @@ XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2)
 
 
 /*======   Canonical representation   ======*/
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API void
-XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash)
+XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash)
 {
     XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t));
     if (XXH_CPU_LITTLE_ENDIAN) {
@@ -5485,9 +6670,9 @@ XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash)
     XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64));
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH128_hash_t
-XXH128_hashFromCanonical(const XXH128_canonical_t* src)
+XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src)
 {
     XXH128_hash_t h;
     h.high64 = XXH_readBE64(src);
@@ -5503,26 +6688,34 @@ XXH128_hashFromCanonical(const XXH128_canonical_t* src)
  */
 #define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x))
 
-static void XXH3_combine16(void* dst, XXH128_hash_t h128)
+XXH_FORCE_INLINE void XXH3_combine16(void* dst, XXH128_hash_t h128)
 {
     XXH_writeLE64( dst, XXH_readLE64(dst) ^ h128.low64 );
     XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 );
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API XXH_errorcode
-XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize)
+XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize)
 {
+#if (XXH_DEBUGLEVEL >= 1)
     XXH_ASSERT(secretBuffer != NULL);
-    if (secretBuffer == NULL) return XXH_ERROR;
     XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+#else
+    /* production mode, assert() are disabled */
+    if (secretBuffer == NULL) return XXH_ERROR;
     if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+#endif
+
     if (customSeedSize == 0) {
         customSeed = XXH3_kSecret;
         customSeedSize = XXH_SECRET_DEFAULT_SIZE;
     }
+#if (XXH_DEBUGLEVEL >= 1)
     XXH_ASSERT(customSeed != NULL);
+#else
     if (customSeed == NULL) return XXH_ERROR;
+#endif
 
     /* Fill secretBuffer with a copy of customSeed - repeat as needed */
     {   size_t pos = 0;
@@ -5546,9 +6739,9 @@ XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSee
     return XXH_OK;
 }
 
-/*! @ingroup xxh3_family */
+/*! @ingroup XXH3_family */
 XXH_PUBLIC_API void
-XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed)
+XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed)
 {
     XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
     XXH3_initCustomSecret(secret, seed);
@@ -5561,7 +6754,7 @@ XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed)
 /* Pop our optimization override from above */
 #if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
   && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
-  && defined(__OPTIMIZE__) && !defined(__OPTIMIZE_SIZE__) /* respect -O0 and -Os */
+  && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */
 #  pragma GCC pop_options
 #endif
 
@@ -5576,5 +6769,5 @@ XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed)
 
 
 #if defined (__cplusplus)
-}
+} /* extern "C" */
 #endif
diff --git a/src/util/BitSet.hpp b/src/util/BitSet.hpp
new file mode 100644 (file)
index 0000000..9cf146d
--- /dev/null
@@ -0,0 +1,121 @@
+// Copyright (C) 2023 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#pragma once
+
+#include <type_traits>
+
+namespace util {
+
+template<typename T> class BitSet
+{
+public:
+  explicit BitSet(T value = {});
+  BitSet(const BitSet& set);
+
+  BitSet& operator=(const BitSet& set);
+
+  bool contains(T value) const;
+  bool empty() const;
+
+  void clear();
+  void insert(T value);
+  void insert(const BitSet& set);
+  void erase(T value);
+
+  template<typename U> static BitSet<T> from_bitmask(U mask);
+  typename std::underlying_type<T>::type to_bitmask() const;
+
+private:
+  typename std::underlying_type<T>::type m_value;
+};
+
+// --- Inline implementations ---
+
+template<typename T>
+inline BitSet<T>::BitSet(T value)
+  : m_value(static_cast<typename std::underlying_type<T>::type>(value))
+{
+}
+
+template<typename T>
+inline BitSet<T>::BitSet(const BitSet& set) : m_value(set.m_value)
+{
+}
+
+template<typename T>
+inline BitSet<T>&
+BitSet<T>::operator=(const BitSet& set)
+{
+  m_value = set.m_value;
+  return *this;
+}
+
+template<typename T>
+inline bool
+BitSet<T>::contains(T value) const
+{
+  return m_value & static_cast<typename std::underlying_type<T>::type>(value);
+}
+
+template<typename T>
+inline bool
+BitSet<T>::empty() const
+{
+  return to_bitmask() == 0;
+}
+
+template<typename T>
+inline void
+BitSet<T>::insert(T value)
+{
+  m_value |= static_cast<typename std::underlying_type<T>::type>(value);
+}
+
+template<typename T>
+inline void
+BitSet<T>::insert(const BitSet& set)
+{
+  m_value |= static_cast<typename std::underlying_type<T>::type>(set.m_value);
+}
+
+template<typename T>
+inline void
+BitSet<T>::erase(T value)
+{
+  m_value &= ~static_cast<typename std::underlying_type<T>::type>(value);
+}
+
+template<typename T>
+template<typename U>
+inline BitSet<T>
+BitSet<T>::from_bitmask(U mask)
+{
+  BitSet result;
+  result.m_value = mask;
+  return result;
+}
+
+template<typename T>
+inline typename std::underlying_type<T>::type
+BitSet<T>::to_bitmask() const
+{
+  return m_value;
+}
+
+} // namespace util
index f06eeee..1cde973 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 namespace util {
 
 Bytes::Bytes(const Bytes& other) noexcept
-  : m_size(other.m_size),
+  : m_data(std::make_unique<uint8_t[]>(other.m_size)),
+    m_size(other.m_size),
     m_capacity(other.m_size)
 {
-  delete[] m_data;
-  m_data = new uint8_t[m_size];
   if (m_size > 0) {
-    std::memcpy(m_data, other.m_data, m_size);
+    std::memcpy(m_data.get(), other.m_data.get(), m_size);
   }
 }
 
 Bytes::Bytes(Bytes&& other) noexcept
+  : m_data(std::move(other.m_data)),
+    m_size(other.m_size),
+    m_capacity(other.m_capacity)
 {
-  delete[] m_data;
-  m_data = other.m_data;
-  m_size = other.m_size;
-  m_capacity = other.m_capacity;
   other.m_data = nullptr;
   other.m_size = 0;
   other.m_capacity = 0;
@@ -50,12 +48,11 @@ Bytes::operator=(const Bytes& other) noexcept
   if (&other == this) {
     return *this;
   }
-  delete[] m_data;
-  m_data = new uint8_t[other.m_size];
+  m_data = std::make_unique<uint8_t[]>(other.m_size);
   m_size = other.m_size;
   m_capacity = other.m_size;
   if (m_size > 0) {
-    std::memcpy(m_data, other.m_data, m_size);
+    std::memcpy(m_data.get(), other.m_data.get(), m_size);
   }
   return *this;
 }
@@ -66,11 +63,10 @@ Bytes::operator=(Bytes&& other) noexcept
   if (&other == this) {
     return *this;
   }
-  delete[] m_data;
-  m_data = other.m_data;
+  m_data = std::move(other.m_data);
   m_size = other.m_size;
   m_capacity = other.m_capacity;
-  other.m_data = nullptr;
+  other.m_data.reset();
   other.m_size = 0;
   other.m_capacity = 0;
   return *this;
@@ -80,12 +76,11 @@ void
 Bytes::reserve(size_t size) noexcept
 {
   if (size > m_capacity) {
-    uint8_t* data = new uint8_t[size];
+    auto data = std::make_unique<uint8_t[]>(size);
     if (m_size > 0) {
-      std::memcpy(data, m_data, m_size);
+      std::memcpy(data.get(), m_data.get(), m_size);
     }
-    delete[] m_data;
-    m_data = data;
+    m_data = std::move(data);
     m_capacity = size;
   }
 }
@@ -99,24 +94,25 @@ Bytes::insert(const uint8_t* pos,
   if (inserted_size == 0) {
     return;
   }
-  const size_t offset = pos - m_data;
+  const size_t offset = pos - m_data.get();
   if (m_size + inserted_size > m_capacity) {
     m_capacity = std::max(2 * m_capacity, m_size + inserted_size);
-    uint8_t* new_data = new uint8_t[m_capacity];
+    auto new_data = std::make_unique<uint8_t[]>(m_capacity);
     if (offset > 0) {
-      std::memcpy(new_data, m_data, offset);
+      std::memcpy(new_data.get(), m_data.get(), offset);
     }
     if (m_size > offset) {
-      std::memcpy(
-        new_data + offset + inserted_size, m_data + offset, m_size - offset);
+      std::memcpy(new_data.get() + offset + inserted_size,
+                  m_data.get() + offset,
+                  m_size - offset);
     }
-    delete[] m_data;
-    m_data = new_data;
+    m_data = std::move(new_data);
   } else if (m_size > offset) {
-    std::memmove(
-      m_data + offset + inserted_size, m_data + offset, m_size - offset);
+    std::memmove(m_data.get() + offset + inserted_size,
+                 m_data.get() + offset,
+                 m_size - offset);
   }
-  std::memcpy(m_data + offset, first, inserted_size);
+  std::memcpy(m_data.get() + offset, first, inserted_size);
   m_size += inserted_size;
 }
 
@@ -124,12 +120,11 @@ void
 Bytes::resize(size_t size) noexcept
 {
   if (size > m_capacity) {
-    uint8_t* new_data = new uint8_t[size];
+    auto new_data = std::make_unique<uint8_t[]>(size);
     if (m_size > 0) {
-      std::memcpy(new_data, m_data, m_size);
+      std::memcpy(new_data.get(), m_data.get(), m_size);
     }
-    delete[] m_data;
-    m_data = new_data;
+    m_data = std::move(new_data);
     m_capacity = size;
   }
   m_size = size;
index ce9a6a2..3d328d4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -23,6 +23,7 @@
 #include <cstdint>
 #include <cstring>
 #include <initializer_list>
+#include <memory>
 
 namespace util {
 
@@ -85,24 +86,24 @@ public:
   void insert(const uint8_t* pos, const char* data, size_t size) noexcept;
 
 private:
-  uint8_t* m_data = nullptr;
+  std::unique_ptr<uint8_t[]> m_data;
   size_t m_size = 0;
   size_t m_capacity = 0;
 };
 
 inline Bytes::Bytes(size_t size) noexcept
-  : m_data(new uint8_t[size]),
+  : m_data(std::make_unique<uint8_t[]>(size)),
     m_size(size),
     m_capacity(size)
 {
 }
 
 inline Bytes::Bytes(const void* data, size_t size) noexcept
-  : m_data(new uint8_t[size]),
+  : m_data(std::make_unique<uint8_t[]>(size)),
     m_size(size),
     m_capacity(size)
 {
-  std::memcpy(m_data, data, size);
+  std::memcpy(m_data.get(), data, size);
 }
 
 inline Bytes::Bytes(nonstd::span<const uint8_t> data) noexcept
@@ -115,10 +116,7 @@ inline Bytes::Bytes(std::initializer_list<uint8_t> init) noexcept
 {
 }
 
-inline Bytes::~Bytes() noexcept
-{
-  delete[] m_data;
-}
+inline Bytes::~Bytes() noexcept = default;
 
 inline uint8_t
 Bytes::operator[](size_t pos) const noexcept
@@ -137,7 +135,7 @@ Bytes::operator==(const Bytes& other) const noexcept
 {
   return this == &other
          || (m_size == other.m_size
-             && std::memcmp(m_data, other.m_data, m_size) == 0);
+             && std::memcmp(m_data.get(), other.m_data.get(), m_size) == 0);
 }
 
 inline bool
@@ -149,49 +147,49 @@ Bytes::operator!=(const Bytes& other) const noexcept
 inline const uint8_t*
 Bytes::data() const noexcept
 {
-  return m_data;
+  return m_data.get();
 }
 
 inline uint8_t*
 Bytes::data() noexcept
 {
-  return m_data;
+  return m_data.get();
 }
 
 inline uint8_t*
 Bytes::begin() noexcept
 {
-  return m_data;
+  return m_data.get();
 }
 
 inline const uint8_t*
 Bytes::begin() const noexcept
 {
-  return m_data;
+  return m_data.get();
 }
 
 inline const uint8_t*
 Bytes::cbegin() const noexcept
 {
-  return m_data;
+  return m_data.get();
 }
 
 inline uint8_t*
 Bytes::end() noexcept
 {
-  return m_data + m_size;
+  return m_data.get() + m_size;
 }
 
 inline const uint8_t*
 Bytes::end() const noexcept
 {
-  return m_data + m_size;
+  return m_data.get() + m_size;
 }
 
 inline const uint8_t*
 Bytes::cend() const noexcept
 {
-  return m_data + m_size;
+  return m_data.get() + m_size;
 }
 
 inline bool
index 560f79f..d08916b 100644 (file)
@@ -2,6 +2,7 @@ set(
   sources
   Bytes.cpp
   LockFile.cpp
+  LongLivedLockFileManager.cpp
   TextTable.cpp
   TimePoint.cpp
   Tokenizer.cpp
index f90b762..43ed32c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -42,7 +42,6 @@ const double k_min_sleep_time = 0.010;
 const double k_max_sleep_time = 0.050;
 #ifndef _WIN32
 const util::Duration k_staleness_limit(2);
-const util::Duration k_keep_alive_interval(k_staleness_limit / 4);
 #endif
 
 namespace {
@@ -75,6 +74,7 @@ namespace util {
 LockFile::LockFile(const std::string& path)
   : m_lock_file(path + ".lock"),
 #ifndef _WIN32
+    m_alive_file(path + ".alive"),
     m_acquired(false)
 #else
     m_handle(INVALID_HANDLE_VALUE)
@@ -82,6 +82,55 @@ LockFile::LockFile(const std::string& path)
 {
 }
 
+LockFile::LockFile(LockFile&& other) noexcept
+  : m_lock_file(std::move(other.m_lock_file)),
+#ifndef _WIN32
+    m_lock_manager(other.m_lock_manager),
+    m_alive_file(std::move(other.m_alive_file)),
+    m_acquired(other.m_acquired)
+#else
+    m_handle(other.m_handle)
+#endif
+{
+#ifndef _WIN32
+  other.m_lock_manager = nullptr;
+  other.m_acquired = false;
+#else
+  other.m_handle = INVALID_HANDLE_VALUE;
+#endif
+}
+
+LockFile&
+LockFile::operator=(LockFile&& other) noexcept
+{
+  if (&other != this) {
+    m_lock_file = std::move(other.m_lock_file);
+#ifndef _WIN32
+    m_lock_manager = other.m_lock_manager;
+    other.m_lock_manager = nullptr;
+    m_alive_file = std::move(other.m_alive_file);
+    m_acquired = other.m_acquired;
+    other.m_acquired = false;
+#else
+    m_handle = other.m_handle;
+    other.m_handle = INVALID_HANDLE_VALUE;
+#endif
+  }
+  return *this;
+}
+
+void
+LockFile::make_long_lived(
+  [[maybe_unused]] LongLivedLockFileManager& lock_manager)
+{
+#ifndef _WIN32
+  m_lock_manager = &lock_manager;
+  if (acquired()) {
+    m_lock_manager->register_alive_file(m_alive_file);
+  }
+#endif
+}
+
 bool
 LockFile::acquire()
 {
@@ -105,7 +154,10 @@ LockFile::release()
 
   LOG("Releasing {}", m_lock_file);
 #ifndef _WIN32
-  on_before_release();
+  if (m_lock_manager) {
+    m_lock_manager->deregister_alive_file(m_alive_file);
+  }
+  Util::unlink_tmp(m_alive_file);
   Util::unlink_tmp(m_lock_file);
 #else
   CloseHandle(m_handle);
@@ -138,9 +190,19 @@ LockFile::acquire(const bool blocking)
 #else
   m_handle = do_acquire(blocking);
 #endif
+
   if (acquired()) {
     LOG("Acquired {}", m_lock_file);
-    on_after_acquire();
+#ifndef _WIN32
+    LOG("Creating {}", m_alive_file);
+    const auto result = util::write_file(m_alive_file, "");
+    if (!result) {
+      LOG("Failed to write {}: {}", m_alive_file, result.error());
+    }
+    if (m_lock_manager) {
+      m_lock_manager->register_alive_file(m_alive_file);
+    }
+#endif
   } else {
     LOG("Failed to acquire lock {}", m_lock_file);
   }
@@ -170,7 +232,7 @@ LockFile::do_acquire(const bool blocking)
   while (true) {
     const auto now = util::TimePoint::now();
     const auto my_content =
-      FMT("{}-{}.{}", content_prefix, now.sec(), now.nsec());
+      FMT("{}-{}.{}", content_prefix, now.sec(), now.nsec_decimal_part());
 
     if (symlink(my_content.c_str(), m_lock_file.c_str()) == 0) {
       // We got the lock.
@@ -223,8 +285,11 @@ LockFile::do_acquire(const bool blocking)
     }
 
     const auto last_lock_update = get_last_lock_update();
-    if (last_lock_update) {
-      last_seen_activity = std::max(last_seen_activity, *last_lock_update);
+    if (last_lock_update && *last_lock_update > last_seen_activity) {
+      if (!blocking) {
+        return false;
+      }
+      last_seen_activity = *last_lock_update;
     }
 
     const util::Duration inactive_duration =
@@ -234,17 +299,14 @@ LockFile::do_acquire(const bool blocking)
       LOG("Lock {} held by another process active {}.{:03} seconds ago",
           m_lock_file,
           inactive_duration.sec(),
-          inactive_duration.nsec() / 1'000'000);
-      if (!blocking) {
-        return false;
-      }
+          inactive_duration.nsec_decimal_part() / 1'000'000);
     } else if (content == initial_content) {
       // The lock seems to be stale -- break it and try again.
       LOG("Breaking {} since it has been inactive for {}.{:03} seconds",
           m_lock_file,
           inactive_duration.sec(),
-          inactive_duration.nsec() / 1'000'000);
-      if (!on_before_break() || !Util::unlink_tmp(m_lock_file)) {
+          inactive_duration.nsec_decimal_part() / 1'000'000);
+      if (!Util::unlink_tmp(m_alive_file) || !Util::unlink_tmp(m_lock_file)) {
         return false;
       }
 
@@ -274,6 +336,16 @@ LockFile::do_acquire(const bool blocking)
   }
 }
 
+std::optional<util::TimePoint>
+LockFile::get_last_lock_update()
+{
+  if (const auto stat = Stat::stat(m_alive_file); stat) {
+    return stat.mtime();
+  } else {
+    return std::nullopt;
+  }
+}
+
 #else // !_WIN32
 
 void*
@@ -333,99 +405,4 @@ LockFile::do_acquire(const bool blocking)
 
 #endif // !_WIN32
 
-ShortLivedLockFile::ShortLivedLockFile(const std::string& path) : LockFile(path)
-{
-}
-
-LongLivedLockFile::LongLivedLockFile(const std::string& path)
-  : LockFile(path)
-#ifndef _WIN32
-    ,
-    m_alive_file(path + ".alive")
-#endif
-{
-}
-
-#ifndef _WIN32
-
-void
-LongLivedLockFile::on_after_acquire()
-{
-  const auto result = util::write_file(m_alive_file, "");
-  if (!result) {
-    LOG("Failed to write {}: {}", m_alive_file, result.error());
-  }
-
-  LOG_RAW("Starting keep-alive thread");
-  m_keep_alive_thread = std::thread([=] {
-    while (true) {
-      std::unique_lock<std::mutex> lock(m_stop_keep_alive_mutex);
-      m_stop_keep_alive_condition.wait_for(
-        lock,
-        std::chrono::seconds(k_keep_alive_interval.sec())
-          + std::chrono::nanoseconds(k_keep_alive_interval.nsec()),
-        [this] { return m_stop_keep_alive; });
-      if (m_stop_keep_alive) {
-        return;
-      }
-      util::set_timestamps(m_alive_file);
-    }
-  });
-  LOG_RAW("Started keep-alive thread");
-}
-
-void
-LongLivedLockFile::on_before_release()
-{
-  if (m_keep_alive_thread.joinable()) {
-    {
-      std::unique_lock<std::mutex> lock(m_stop_keep_alive_mutex);
-      m_stop_keep_alive = true;
-    }
-    m_stop_keep_alive_condition.notify_one();
-    m_keep_alive_thread.join();
-
-    Util::unlink_tmp(m_alive_file);
-  }
-}
-
-bool
-LongLivedLockFile::on_before_break()
-{
-  return Util::unlink_tmp(m_alive_file);
-}
-
-std::optional<util::TimePoint>
-LongLivedLockFile::get_last_lock_update()
-{
-  if (const auto stat = Stat::stat(m_alive_file); stat) {
-    return stat.mtime();
-  } else {
-    return std::nullopt;
-  }
-}
-
-#endif
-
-LockFileGuard::LockFileGuard(LockFile& lock_file, Mode mode)
-  : m_lock_file(lock_file)
-{
-  if (mode == Mode::blocking) {
-    lock_file.acquire();
-  } else {
-    lock_file.try_acquire();
-  }
-}
-
-LockFileGuard::~LockFileGuard() noexcept
-{
-  m_lock_file.release();
-}
-
-bool
-LockFileGuard::acquired() const
-{
-  return m_lock_file.acquired();
-}
-
 } // namespace util
index 705dd7b..865b9dd 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 #pragma once
 
 #include <NonCopyable.hpp>
+#include <util/LongLivedLockFileManager.hpp>
 #include <util/TimePoint.hpp>
 
-#include <condition_variable>
-#include <cstdint>
 #include <optional>
 #include <string>
-#include <thread>
 
 namespace util {
 
+// Unless make_long_lived is called, the lock is expected to be released shortly
+// after being acquired - if it is held for more than two seconds it risks being
+// considered stale by another client.
 class LockFile : NonCopyable
 {
 public:
-  virtual ~LockFile() noexcept = default;
+  explicit LockFile(const std::string& path);
+  LockFile(LockFile&& other) noexcept;
+
+  LockFile& operator=(LockFile&& other) noexcept;
+
+  // Release the lock if previously acquired.
+  ~LockFile();
+
+  // Make this lock long-lived. Depending on implementation, it will be kept
+  // alive by a helper thread.
+  void make_long_lived(LongLivedLockFileManager& lock_manager);
 
   // Acquire lock, blocking. Returns true if acquired, otherwise false.
-  bool acquire();
+  [[nodiscard]] bool acquire();
 
   // Acquire lock, non-blocking. Returns true if acquired, otherwise false.
-  bool try_acquire();
+  [[nodiscard]] bool try_acquire();
 
-  // Release lock. If not previously acquired, nothing happens.
+  // Release lock early. If not previously acquired, nothing happens.
   void release();
 
-  // Return whether the lock was acquired successfully.
+  // Return whether the lock is acquired successfully.
   bool acquired() const;
 
-protected:
-  LockFile(const std::string& path);
-
 private:
   std::string m_lock_file;
 #ifndef _WIN32
+  LongLivedLockFileManager* m_lock_manager = nullptr;
+  std::string m_alive_file;
   bool m_acquired;
 #else
   void* m_handle;
 #endif
 
   bool acquire(bool blocking);
-  virtual void on_after_acquire();
-  virtual void on_before_release();
 #ifndef _WIN32
   bool do_acquire(bool blocking);
-  virtual bool on_before_break();
-  virtual std::optional<util::TimePoint> get_last_lock_update();
+  std::optional<util::TimePoint> get_last_lock_update();
 #else
   void* do_acquire(bool blocking);
 #endif
 };
 
-// A short-lived lock.
-//
-// The lock is expected to be released shortly after being acquired - if it is
-// held for more than two seconds it risks being considered stale by another
-// client.
-class ShortLivedLockFile : public LockFile
-{
-public:
-  ShortLivedLockFile(const std::string& path);
-};
-
-// A long-lived lock.
-//
-// The lock will (depending on implementation) be kept alive by a helper thread.
-class LongLivedLockFile : public LockFile
-{
-public:
-  LongLivedLockFile(const std::string& path);
-
-private:
-#ifndef _WIN32
-  std::string m_alive_file;
-  std::thread m_keep_alive_thread;
-  std::mutex m_stop_keep_alive_mutex;
-  bool m_stop_keep_alive = false;
-  std::condition_variable m_stop_keep_alive_condition;
-
-  void on_after_acquire() override;
-  void on_before_release() override;
-  bool on_before_break() override;
-  std::optional<util::TimePoint> get_last_lock_update() override;
-#endif
-};
-
-class LockFileGuard : NonCopyable
-{
-public:
-  enum class Mode { blocking, non_blocking };
-
-  LockFileGuard(LockFile& lock_file, Mode mode = Mode::blocking);
-  ~LockFileGuard() noexcept;
-
-  bool acquired() const;
-
-private:
-  LockFile& m_lock_file;
-};
-
-inline void
-LockFile::on_after_acquire()
-{
-}
-
-inline void
-LockFile::on_before_release()
+inline LockFile::~LockFile()
 {
+  release();
 }
 
-#ifndef _WIN32
-
-inline bool
-LockFile::on_before_break()
-{
-  return true;
-}
-
-inline std::optional<util::TimePoint>
-LockFile::get_last_lock_update()
-{
-  return std::nullopt;
-}
-
-#endif
-
 } // namespace util
diff --git a/src/util/LongLivedLockFileManager.cpp b/src/util/LongLivedLockFileManager.cpp
new file mode 100644 (file)
index 0000000..4c2d87d
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "LongLivedLockFileManager.hpp"
+
+#include <Logging.hpp>
+#include <Util.hpp>
+#include <util/file.hpp>
+
+#include <chrono>
+
+#ifndef _WIN32
+std::chrono::milliseconds k_keep_alive_interval{500};
+#endif
+
+namespace util {
+
+LongLivedLockFileManager::~LongLivedLockFileManager()
+{
+#ifndef _WIN32
+  if (m_thread.joinable()) {
+    LOG_RAW("Stopping keep-alive thread");
+    {
+      std::unique_lock<std::mutex> lock(m_mutex);
+      m_stop = true;
+    }
+    m_stop_condition.notify_one();
+    m_thread.join();
+    LOG_RAW("Stopped keep-alive thread");
+  }
+#endif
+}
+
+void
+LongLivedLockFileManager::register_alive_file(
+  [[maybe_unused]] const std::string& path)
+{
+#ifndef _WIN32
+  std::unique_lock<std::mutex> lock(m_mutex);
+  if (!m_thread.joinable()) {
+    start_thread();
+  }
+  m_alive_files.insert(path);
+#endif
+}
+
+void
+LongLivedLockFileManager::deregister_alive_file(
+  [[maybe_unused]] const std::string& path)
+{
+#ifndef _WIN32
+  std::unique_lock<std::mutex> lock(m_mutex);
+  m_alive_files.erase(path);
+#endif
+}
+
+#ifndef _WIN32
+void
+LongLivedLockFileManager::start_thread()
+{
+  LOG_RAW("Starting keep-alive thread");
+  m_thread = std::thread([&] {
+    auto awake_time = std::chrono::steady_clock::now();
+    while (true) {
+      std::unique_lock<std::mutex> lock(m_mutex);
+      m_stop_condition.wait_until(lock, awake_time, [this] { return m_stop; });
+      if (m_stop) {
+        return;
+      }
+      for (const auto& alive_file : m_alive_files) {
+        util::set_timestamps(alive_file);
+      }
+      awake_time += k_keep_alive_interval;
+    }
+  });
+  LOG_RAW("Started keep-alive thread");
+}
+#endif
+
+} // namespace util
similarity index 53%
rename from src/storage/local/CacheFile.cpp
rename to src/util/LongLivedLockFileManager.hpp
index a2716e8..2120973 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 // this program; if not, write to the Free Software Foundation, Inc., 51
 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
-#include "CacheFile.hpp"
+#pragma once
 
-#include <core/Manifest.hpp>
-#include <core/Result.hpp>
-#include <util/string.hpp>
+#include <NonCopyable.hpp>
 
-const Stat&
-CacheFile::lstat() const
-{
-  if (!m_stat) {
-    m_stat = Stat::lstat(m_path);
-  }
+#include <condition_variable>
+#include <mutex>
+#include <set>
+#include <string>
+#include <thread>
 
-  return *m_stat;
-}
+namespace util {
 
-CacheFile::Type
-CacheFile::type() const
+class LongLivedLockFileManager : NonCopyable
 {
-  if (util::ends_with(m_path, "M")) {
-    return Type::manifest;
-  } else if (util::ends_with(m_path, "R")) {
-    return Type::result;
-  } else if (util::ends_with(m_path, "W")) {
-    return Type::raw;
-  } else {
-    return Type::unknown;
-  }
-}
+public:
+  LongLivedLockFileManager() = default;
+  ~LongLivedLockFileManager();
+
+  void register_alive_file(const std::string& path);
+  void deregister_alive_file(const std::string& path);
+
+private:
+#ifndef _WIN32
+  std::thread m_thread;
+  std::mutex m_mutex;
+  std::condition_variable m_stop_condition;
+  bool m_stop = false;
+  std::set<std::string> m_alive_files;
+
+  void start_thread();
+#endif
+};
+
+} // namespace util
index 618f7b7..c7fd17f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -32,6 +32,7 @@ TextTable::add_heading(const std::string& text)
   Cell cell(text);
   cell.m_heading = true;
   m_rows.push_back({cell});
+  m_columns = std::max(m_columns, size_t(1));
 }
 
 void
@@ -86,6 +87,8 @@ TextTable::render() const
 
   std::string result;
   for (const auto& row : m_rows) {
+    ASSERT(column_widths.size() >= row.size());
+
     std::string r;
     bool first = true;
     for (size_t i = 0; i < row.size(); ++i) {
@@ -112,9 +115,11 @@ TextTable::render() const
   return result;
 }
 
-TextTable::Cell::Cell(const std::string& text)
-  : m_text(text),
-    m_right_align(false)
+TextTable::Cell::Cell(const std::string& text) : m_text(text)
+{
+}
+
+TextTable::Cell::Cell(std::string_view text) : Cell(std::string(text))
 {
 }
 
index 60edee7..d435603 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -20,6 +20,7 @@
 
 #include <cstdint>
 #include <string>
+#include <string_view>
 #include <vector>
 
 namespace util {
@@ -31,6 +32,7 @@ public:
   {
   public:
     Cell(const std::string& text);
+    Cell(std::string_view text);
     Cell(const char* text);
     Cell(uint64_t number);
 
index 7b71896..57a6b56 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -214,6 +214,11 @@ template<typename T>
 nonstd::expected<T, std::string>
 read_file_part(const std::string& path, size_t pos, size_t count)
 {
+  T result;
+  if (count == 0) {
+    return result;
+  }
+
   Fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
   if (!fd) {
     LOG("Failed to open {}: {}", path, strerror(errno));
@@ -226,7 +231,6 @@ read_file_part(const std::string& path, size_t pos, size_t count)
 
   int64_t ret = 0;
   size_t bytes_read = 0;
-  T result;
   result.resize(count);
 
   while (true) {
@@ -255,6 +259,9 @@ read_file_part(const std::string& path, size_t pos, size_t count)
 template nonstd::expected<util::Bytes, std::string>
 read_file_part(const std::string& path, size_t pos, size_t count);
 
+template nonstd::expected<std::string, std::string>
+read_file_part(const std::string& path, size_t pos, size_t count);
+
 template nonstd::expected<std::vector<uint8_t>, std::string>
 read_file_part(const std::string& path, size_t pos, size_t count);
 
index bb0c8b2..0ae0a57 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -39,6 +39,9 @@ enum class InPlace { yes, no };
 
 void create_cachedir_tag(const std::string& dir);
 
+// Return how much a file of `size` bytes likely would take on disk.
+uint64_t likely_size_on_disk(uint64_t size);
+
 // Read data from `fd` until end of file and call `data_receiver` with the read
 // data. Returns an error if the underlying read(2) call returned -1.
 nonstd::expected<void, std::string> read_fd(int fd, DataReceiver data_receiver);
@@ -58,9 +61,10 @@ nonstd::expected<T, std::string> read_file(const std::string& path,
 
 // Return (at most) `count` bytes from `path` starting at position `pos`.
 //
-// `T` should be `util::Bytes` or `std::vector<uint8_t>`. If `T` is
-// `std::string` and the content starts with a UTF-16 little-endian BOM on
-// Windows then it will be converted to UTF-8.
+// `T` should be `util::Bytes` or `std::vector<uint8_t>` for binary data and
+// `std::string` for text data. If `T` is `std::string` and the content starts
+// with a UTF-16 little-endian BOM on Windows then it will be converted to
+// UTF-8.
 template<typename T>
 nonstd::expected<T, std::string>
 read_file_part(const std::string& path, size_t pos, size_t count);
@@ -87,4 +91,12 @@ nonstd::expected<void, std::string> write_file(const std::string& path,
                                                nonstd::span<const uint8_t> data,
                                                InPlace in_place = InPlace::no);
 
+// --- Inline implementations ---
+
+inline uint64_t
+likely_size_on_disk(uint64_t size)
+{
+  return (size + 4095) & ~4095;
+}
+
 } // namespace util
index a1e797d..d1e6b7e 100644 (file)
@@ -73,10 +73,14 @@ path_starts_with(std::string_view path, std::string_view prefix)
     if (path[i] == '\\' && prefix[j] == '/') {
       continue;
     }
-#endif
+    if (std::tolower(path[i]) != std::tolower(prefix[j])) {
+      return false;
+    }
+#else
     if (path[i] != prefix[j]) {
       return false;
     }
+#endif
   }
   return true;
 }
index 3a03a0b..48d4b8b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 namespace util {
 
+std::string
+format_human_readable_diff(int64_t diff, SizeUnitPrefixType prefix_type)
+{
+  const char* sign = diff == 0 ? "" : (diff > 0 ? "+" : "-");
+  return FMT(
+    "{}{}", sign, format_human_readable_size(std::abs(diff), prefix_type));
+}
+
+std::string
+format_human_readable_size(uint64_t size, SizeUnitPrefixType prefix_type)
+{
+  const double factor = prefix_type == SizeUnitPrefixType::binary ? 1024 : 1000;
+  const char* infix = prefix_type == SizeUnitPrefixType::binary ? "i" : "";
+  if (size >= factor * factor * factor) {
+    return FMT("{:.1f} G{}B", size / (factor * factor * factor), infix);
+  } else if (size >= factor * factor) {
+    return FMT("{:.1f} M{}B", size / (factor * factor), infix);
+  } else if (size >= factor) {
+    const char* k = prefix_type == SizeUnitPrefixType::binary ? "K" : "k";
+    return FMT("{:.1f} {}{}B", size / factor, k, infix);
+  } else if (size == 1) {
+    return "1 byte";
+  } else {
+    return FMT("{} bytes", size);
+  }
+}
+
 nonstd::expected<double, std::string>
 parse_double(const std::string& value)
 {
@@ -79,6 +106,52 @@ parse_signed(std::string_view value,
   }
 }
 
+nonstd::expected<std::pair<uint64_t, SizeUnitPrefixType>, std::string>
+parse_size(const std::string& value)
+{
+  errno = 0;
+
+  char* p;
+  double result = strtod(value.c_str(), &p);
+  if (errno != 0 || result < 0 || p == value.c_str() || value.empty()) {
+    return nonstd::make_unexpected(FMT("invalid size: \"{}\"", value));
+  }
+
+  while (isspace(*p)) {
+    ++p;
+  }
+
+  SizeUnitPrefixType prefix_type;
+  if (*p != '\0') {
+    prefix_type = *(p + 1) == 'i' ? SizeUnitPrefixType::binary
+                                  : SizeUnitPrefixType::decimal;
+    unsigned multiplier =
+      prefix_type == SizeUnitPrefixType::binary ? 1024 : 1000;
+    switch (*p) {
+    case 'T':
+      result *= multiplier;
+      [[fallthrough]];
+    case 'G':
+      result *= multiplier;
+      [[fallthrough]];
+    case 'M':
+      result *= multiplier;
+      [[fallthrough]];
+    case 'K':
+    case 'k':
+      result *= multiplier;
+      break;
+    default:
+      return nonstd::make_unexpected(FMT("invalid size: \"{}\"", value));
+    }
+  } else {
+    result *= 1024 * 1024 * 1024;
+    prefix_type = SizeUnitPrefixType::binary;
+  }
+
+  return std::make_pair(static_cast<uint64_t>(result), prefix_type);
+}
+
 nonstd::expected<mode_t, std::string>
 parse_umask(std::string_view value)
 {
@@ -201,6 +274,23 @@ replace_first(const std::string_view string,
 }
 
 std::pair<std::string_view, std::optional<std::string_view>>
+split_once(const char* string, const char split_char)
+{
+  return split_once(std::string_view(string), split_char);
+}
+
+std::pair<std::string, std::optional<std::string>>
+split_once(std::string&& string, const char split_char)
+{
+  const auto [left, right] = split_once(std::string_view(string), split_char);
+  if (right) {
+    return std::make_pair(std::string(left), std::string(*right));
+  } else {
+    return std::make_pair(std::string(left), std::nullopt);
+  }
+}
+
+std::pair<std::string_view, std::optional<std::string_view>>
 split_once(const std::string_view string, const char split_char)
 {
   const size_t sep_pos = string.find(split_char);
index 0e48bc4..6e1fcf4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -36,9 +36,19 @@ namespace util {
 
 // --- Interface ---
 
+enum class SizeUnitPrefixType { binary, decimal };
+
 // Return true if `suffix` is a suffix of `string`.
 bool ends_with(std::string_view string, std::string_view suffix);
 
+// Format `diff` as a human-readable string.
+std::string format_human_readable_diff(int64_t diff,
+                                       SizeUnitPrefixType prefix_type);
+
+// Format `size` as a human-readable string.
+std::string format_human_readable_size(uint64_t size,
+                                       SizeUnitPrefixType prefix_type);
+
 // Join stringified elements of `container` delimited by `delimiter` into a
 // string. There must exist an `std::string to_string(T::value_type)` function.
 template<typename T>
@@ -68,6 +78,12 @@ parse_signed(std::string_view value,
              std::optional<int64_t> max_value = std::nullopt,
              std::string_view description = "integer");
 
+// Parse a "size value", i.e. a string that can end in k, M, G, T (10-based
+// suffixes) or Ki, Mi, Gi, Ti (2-based suffixes). For backward compatibility, K
+// is also recognized as a synonym of k.
+nonstd::expected<std::pair<uint64_t, util::SizeUnitPrefixType>, std::string>
+parse_size(const std::string& value);
+
 // Parse `value` (an octal integer).
 nonstd::expected<mode_t, std::string> parse_umask(std::string_view value);
 
@@ -103,6 +119,10 @@ std::string replace_first(std::string_view string,
 // Split `string` into two parts using `split_char` as the delimiter. The second
 // part will be `nullopt` if there is no `split_char` in `string.`
 std::pair<std::string_view, std::optional<std::string_view>>
+split_once(const char* string, char split_char);
+std::pair<std::string, std::optional<std::string>>
+split_once(std::string&& string, char split_char);
+std::pair<std::string_view, std::optional<std::string_view>>
 split_once(std::string_view string, char split_char);
 
 // Return true if `prefix` is a prefix of `string`.
index 5dc123f..43a5731 100755 (executable)
--- a/test/run
+++ b/test/run
@@ -3,7 +3,7 @@
 # A simple test suite for ccache.
 #
 # Copyright (C) 2002-2007 Andrew Tridgell
-# Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+# Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 #
 # See doc/AUTHORS.adoc for a complete list of contributors.
 #
@@ -161,11 +161,11 @@ objdump_cmd() {
 
 objdump_grep_cmd() {
     if $HOST_OS_APPLE; then
-        fgrep -q "\"$1\""
+        grep -Fq "\"$1\""
     elif $HOST_OS_WINDOWS || $HOST_OS_CYGWIN; then
-        fgrep -q "$1"
+        grep -Fq "$1"
     else
-        fgrep -q ": $1"
+        grep -Fq ": $1"
     fi
 }
 
@@ -324,7 +324,7 @@ expect_contains() {
     if [ ! -e "$file" ]; then
         test_failed_internal "$file not found"
     fi
-    if ! fgrep -q -- "$string" "$file"; then
+    if ! grep -Fq -- "$string" "$file"; then
         test_failed_internal "File $file does not contain \"$string\"\nActual content: $(cat $file)"
     fi
 }
@@ -336,7 +336,7 @@ expect_not_contains() {
     if [ ! -e "$file" ]; then
         test_failed_internal "$file not found"
     fi
-    if fgrep -q -- "$string" "$file"; then
+    if grep -Fq -- "$string" "$file"; then
         test_failed_internal "File $file contains \"$string\"\nActual content: $(cat $file)"
     fi
 }
index 2d14c95..17056a4 100644 (file)
@@ -393,6 +393,7 @@ if $RUN_WIN_XFAIL;then
     CCACHE_DEBUG=1 CCACHE_DEBUGDIR=debugdir $CCACHE_COMPILE -c test1.c
     expect_contains debugdir"$(pwd -P)"/test1.o.*.ccache-log "Result: cache_miss"
 fi
+
     # -------------------------------------------------------------------------
     TEST "CCACHE_DISABLE"
 
@@ -402,6 +403,13 @@ fi
     fi
 
     # -------------------------------------------------------------------------
+    TEST "ccache:disable"
+
+    echo '// ccache:disable' >>test1.c
+    $CCACHE_COMPILE -c test1.c 2>/dev/null
+    expect_stat disabled 1
+
+    # -------------------------------------------------------------------------
     TEST "CCACHE_COMMENTS"
 
     $COMPILER -c -o reference_test1.o test1.c
@@ -544,8 +552,10 @@ fi
     # -------------------------------------------------------------------------
     TEST "Directory is not hashed if using -gz"
 
-    $COMPILER -E test1.c -gz >preprocessed.i 2>/dev/null
-    if [ -s preprocessed.i ] && ! fgrep -q $PWD preprocessed.i; then
+    if $COMPILER -c test1.c -gz 2>/dev/null \
+       && $COMPILER -E test1.c -gz >preprocessed.i 2>/dev/null \
+       && [ -s preprocessed.i ] \
+       && ! grep -Fq $PWD preprocessed.i; then
         mkdir dir1 dir2
         cp test1.c dir1
         cp test1.c dir2
@@ -572,7 +582,7 @@ fi
     if [ $? -eq 0 ]; then
         # run test only if -gz=zlib is supported
         $COMPILER -E test1.c -gz=zlib >preprocessed.i 2>/dev/null
-        if [ "$exit_code" == "0" ] && [ -s preprocessed.i ] && ! fgrep -q $PWD preprocessed.i; then
+        if [ "$exit_code" == "0" ] && [ -s preprocessed.i ] && ! grep -Fq $PWD preprocessed.i; then
             mkdir dir1 dir2
             cp test1.c dir1
             cp test1.c dir2
@@ -1520,11 +1530,21 @@ EOF
     expect_stat preprocessed_cache_hit 0
     expect_stat cache_miss 1
 
-    $CCACHE_COMPILE -c -DFOO test1.c
+    $CCACHE_COMPILE -c -Wp,-DFOO test1.c
     expect_stat direct_cache_hit 0
     expect_stat preprocessed_cache_hit 1
     expect_stat cache_miss 1
 
+    $CCACHE_COMPILE -c -DFOO test1.c
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 1
+    expect_stat cache_miss 2
+
+    $CCACHE_COMPILE -c -DFOO test1.c
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 2
+    expect_stat cache_miss 2
+
     # -------------------------------------------------------------------------
     if touch empty.c && $COMPILER -c -- empty.c 2>/dev/null; then
         TEST "--"
index 18c2055..53189f2 100644 (file)
-prepare_cleanup_test_dir() {
-    local dir=$1
-    local i
-
-    rm -rf $dir
-    mkdir -p $dir
-    for ((i = 0; i < 10; ++i)); do
-        printf 'A%.0s' {1..4017} >$dir/result${i}R
-        backdate $((3 * i + 1)) $dir/result${i}R
+SUITE_cleanup_PROBE() {
+    # NOTE: This test suite is known to fail on filesystems that have unusual
+    # block sizes, including ecryptfs. The workaround is to place the test
+    # directory elsewhere:
+    #
+    #     cd /tmp
+    #     CCACHE=$DIR/ccache $DIR/test.sh
+    if [ -z "$ENABLE_CACHE_CLEANUP_TESTS" ]; then
+        echo "ENABLE_CACHE_CLEANUP_TESTS is not set"
+    fi
+}
+
+SUITE_cleanup_SETUP() {
+    mkdir -p $CCACHE_DIR/0/0
+    printf 'A%.0s' {1..4017} >"$CCACHE_DIR/0/0/result0R"
+    backdate "$CCACHE_DIR/0/0/result0R"
+    for ((i = 1; i < 10; ++i )); do
+        cp -a "$CCACHE_DIR/0/0/result0R" "$CCACHE_DIR/0/0/result${i}R"
+    done
+
+    subdirs=(1 2 3 4 5 6 7 8 9 a b c d e f)
+    for c in "${subdirs[@]}"; do
+        cp -a "$CCACHE_DIR/0/0" "$CCACHE_DIR/0/${c}"
     done
-    # NUMFILES: 10, TOTALSIZE: 13 KiB, MAXFILES: 0, MAXSIZE: 0
-    echo "0 0 0 0 0 0 0 0 0 0 0 10 13 0 0" >$dir/stats
+
+    for c in "${subdirs[@]}"; do
+        cp -a "$CCACHE_DIR/0" "$CCACHE_DIR/${c}"
+    done
+
+    $CCACHE -c >/dev/null
+
+    # We have now created 16 * 16 * 10 = 2560 files, each 4017 bytes big (4096
+    # bytes on disk), totalling (counting disk blocks) 2560 * 4096 = 10 MiB =
+    # 10240 KiB.
 }
 
 SUITE_cleanup() {
     # -------------------------------------------------------------------------
     TEST "Clear cache"
 
-    prepare_cleanup_test_dir $CCACHE_DIR/a
-
+    expect_stat cleanups_performed 0
     $CCACHE -C >/dev/null
     expect_file_count 0 '*R' $CCACHE_DIR
     expect_stat files_in_cache 0
-    expect_stat cleanups_performed 1
+    expect_stat cleanups_performed 256
 
     # -------------------------------------------------------------------------
-    TEST "Forced cache cleanup, no limits"
-
-    prepare_cleanup_test_dir $CCACHE_DIR/a
+    TEST "Forced cache cleanup, no size limit"
 
-    $CCACHE -F 0 -M 0 >/dev/null
-    $CCACHE -c >/dev/null
-    expect_file_count 10 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 10
+    $CCACHE -M 0 -c >/dev/null
+    expect_file_count 2560 '*R' $CCACHE_DIR
+    expect_stat files_in_cache 2560
     expect_stat cleanups_performed 0
 
     # -------------------------------------------------------------------------
     TEST "Forced cache cleanup, file limit"
 
-    prepare_cleanup_test_dir $CCACHE_DIR/a
+    $CCACHE -F 2543 -c >/dev/null
 
-    # No cleanup needed.
-    #
-    # 10 * 16 = 160
-    $CCACHE -F 160 -M 0 >/dev/null
-    $CCACHE -c >/dev/null
-    expect_file_count 10 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 10
-    expect_stat cleanups_performed 0
-
-    # Reduce file limit
-    #
-    # 7 * 16 = 112
-    $CCACHE -F 112 -M 0 >/dev/null
-    $CCACHE -c >/dev/null
-    expect_file_count 7 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 7
-    expect_stat cleanups_performed 1
-    for i in 0 1 2; do
-        file=$CCACHE_DIR/a/result${i}R
-        expect_missing $CCACHE_DIR/a/result${i}R
-    done
-    for i in 3 4 5 6 7 8 9; do
-        file=$CCACHE_DIR/a/result${i}R
-        expect_exists $file
-    done
+    expect_file_count 2543 '*R' $CCACHE_DIR
+    expect_stat files_in_cache 2543
+    expect_stat cleanups_performed 17
 
     # -------------------------------------------------------------------------
-    if [ -n "$ENABLE_CACHE_CLEANUP_TESTS" ]; then
-        TEST "Forced cache cleanup, size limit"
-
-        # NOTE: This test is known to fail on filesystems that have unusual block
-        # sizes, including ecryptfs. The workaround is to place the test directory
-        # elsewhere:
-        #
-        #     cd /tmp
-        #     CCACHE=$DIR/ccache $DIR/test.sh
-
-        prepare_cleanup_test_dir $CCACHE_DIR/a
-
-        $CCACHE -F 0 -M 256K >/dev/null
-        $CCACHE -c >/dev/null
-        expect_file_count 3 '*R' $CCACHE_DIR
-        expect_stat files_in_cache 3
-        expect_stat cleanups_performed 1
-        for i in 0 1 2 3 4 5 6; do
-            file=$CCACHE_DIR/a/result${i}R
-            expect_missing $file
-        done
-        for i in 7 8 9; do
-            file=$CCACHE_DIR/a/result${i}R
-            expect_exists $file
-        done
-    fi
-    # -------------------------------------------------------------------------
-    TEST "Automatic cache cleanup, limit_multiple 0.9"
-
-    for x in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
-        prepare_cleanup_test_dir $CCACHE_DIR/$x
-    done
+    TEST "Forced cache cleanup, size limit"
 
-    $CCACHE -F 160 -M 0 >/dev/null
+    # 10240 KiB - 10230 KiB = 10 KiB, so we need to remove 3 files of 4 KiB byte
+    # to get under the limit. Each cleanup only removes one file since there are
+    # only 10 files in each directory, so there are 3 cleanups.
+    $CCACHE -M 10230KiB -c >/dev/null
 
-    expect_file_count 160 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 160
-    expect_stat cleanups_performed 0
-
-    touch empty.c
-    CCACHE_LIMIT_MULTIPLE=0.9 $CCACHE_COMPILE -c empty.c -o empty.o
-    expect_file_count 159 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 159
-    expect_stat cleanups_performed 1
+    expect_file_count 2557 '*R' $CCACHE_DIR
+    expect_stat files_in_cache 2557
+    expect_stat cleanups_performed 3
 
     # -------------------------------------------------------------------------
-    TEST "Automatic cache cleanup, limit_multiple 0.7"
+    TEST "Automatic cache cleanup, file limit"
 
-    for x in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
-        prepare_cleanup_test_dir $CCACHE_DIR/$x
-    done
-
-    $CCACHE -F 160 -M 0 >/dev/null
+    $CCACHE -F 2543 >/dev/null
 
-    expect_file_count 160 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 160
-    expect_stat cleanups_performed 0
-
-    touch empty.c
-    CCACHE_LIMIT_MULTIPLE=0.7 $CCACHE_COMPILE -c empty.c -o empty.o
-    expect_file_count 157 '*R' $CCACHE_DIR
-    expect_stat files_in_cache 157
+    touch test.c
+    $CCACHE_COMPILE -c test.c
+    expect_stat files_in_cache 2559
     expect_stat cleanups_performed 1
 
     # -------------------------------------------------------------------------
-    TEST "No cleanup of new unknown file"
-
-    prepare_cleanup_test_dir $CCACHE_DIR/a
-
-    touch $CCACHE_DIR/a/abcd.unknown
-    $CCACHE -F 0 -M 0 -c >/dev/null # update counters
-    expect_stat files_in_cache 11
+    TEST "Automatic cache cleanup, size limit"
 
-    $CCACHE -F 160 -M 0 >/dev/null
-    $CCACHE -c >/dev/null
-    expect_exists $CCACHE_DIR/a/abcd.unknown
-    expect_stat files_in_cache 10
-
-    # -------------------------------------------------------------------------
-    TEST "Cleanup of old unknown file"
+    $CCACHE -M 10230KiB >/dev/null
 
-    prepare_cleanup_test_dir $CCACHE_DIR/a
-    $CCACHE -F 160 -M 0 >/dev/null
-    touch $CCACHE_DIR/a/abcd.unknown
-    backdate $CCACHE_DIR/a/abcd.unknown
-    $CCACHE -F 0 -M 0 -c >/dev/null # update counters
-    expect_stat files_in_cache 11
+    # Automatic cleanup triggers one cleanup. The directory where the result
+    # ended up will have 11 files and will be trimmed down to floor(0.9 * 2561 /
+    # 256) = 9 files.
 
-    $CCACHE -F 160 -M 0 -c >/dev/null
-    expect_missing $CCACHE_DIR/a/abcd.unknown
-    expect_stat files_in_cache 10
+    touch test.c
+    $CCACHE_COMPILE -c test.c
+    expect_stat files_in_cache 2559
+    expect_stat cleanups_performed 1
 
     # -------------------------------------------------------------------------
     TEST "Cleanup of tmp file"
 
-    mkdir -p $CCACHE_DIR/a
-    touch $CCACHE_DIR/a/abcd.tmp.efgh
+    mkdir -p $CCACHE_DIR/a/a
+    touch $CCACHE_DIR/a/a/abcd.tmp.efgh
     $CCACHE -c >/dev/null # update counters
-    expect_stat files_in_cache 1
-    backdate $CCACHE_DIR/a/abcd.tmp.efgh
+    expect_stat files_in_cache 2561
+
+    backdate $CCACHE_DIR/a/a/abcd.tmp.efgh
     $CCACHE -c >/dev/null
-    expect_missing $CCACHE_DIR/a/abcd.tmp.efgh
-    expect_stat files_in_cache 0
+    expect_missing $CCACHE_DIR/a/a/abcd.tmp.efgh
+    expect_stat files_in_cache 2560
 
     # -------------------------------------------------------------------------
     TEST "No cleanup of .nfs* files"
 
-    prepare_cleanup_test_dir $CCACHE_DIR/a
-
-    touch $CCACHE_DIR/a/.nfs0123456789
-    $CCACHE -F 0 -M 0 >/dev/null
+    mkdir -p $CCACHE_DIR/a/a
+    touch $CCACHE_DIR/a/a/.nfs0123456789
     $CCACHE -c >/dev/null
     expect_file_count 1 '.nfs*' $CCACHE_DIR
-    expect_stat files_in_cache 10
+    expect_stat files_in_cache 2560
 
     # -------------------------------------------------------------------------
     TEST "Cleanup of old files by age"
 
-    prepare_cleanup_test_dir $CCACHE_DIR/a
-    touch $CCACHE_DIR/a/nowR
-    $CCACHE -F 0 -M 0 >/dev/null
+    mkdir -p $CCACHE_DIR/a/a
+    touch $CCACHE_DIR/a/a/nowR
 
     $CCACHE --evict-older-than 1d >/dev/null
     expect_file_count 1 '*R' $CCACHE_DIR
@@ -196,7 +133,7 @@ SUITE_cleanup() {
     expect_file_count 1 '*R' $CCACHE_DIR
     expect_stat files_in_cache 1
 
-    backdate $CCACHE_DIR/a/nowR
+    backdate $CCACHE_DIR/a/a/nowR
     $CCACHE --evict-older-than 10s  >/dev/null
     expect_stat files_in_cache 0
 }
index 8295eb1..53a9c2b 100644 (file)
@@ -40,8 +40,8 @@ SUITE_color_diagnostics_SETUP() {
 
 color_diagnostics_expect_color() {
     expect_contains "${1:?}" $'\033['
-    expect_contains <(fgrep 'Wreturn-type' "$1") $'\033['
-    expect_contains <(fgrep 'from preprocessor' "$1") $'\033['
+    expect_contains <(grep -F 'Wreturn-type' "$1") $'\033['
+    expect_contains <(grep -F 'from preprocessor' "$1") $'\033['
 }
 
 color_diagnostics_expect_no_color() {
index 97da52c..7a21f4b 100644 (file)
@@ -8,4 +8,16 @@ SUITE_config() {
     $CCACHE --show-config >config.txt
 
     expect_contains config.txt "(environment) max_size = 40"
+
+    # -------------------------------------------------------------------------
+    TEST "Command line origin"
+
+    export CCACHE_DEBUG="1"
+    export CCACHE_MAXSIZE="40"
+
+    touch test.c
+    $CCACHE debug=true "max_size = 40" $COMPILER -c test.c
+
+    expect_contains test.o.*.ccache-log "(command line) debug = true"
+    expect_contains test.o.*.ccache-log "(command line) max_size = 40"
 }
index 03a14af..37cff13 100644 (file)
@@ -357,4 +357,17 @@ EOF
     expect_stat preprocessed_cache_hit 0
     expect_stat cache_miss 1
     expect_equal_content 'file with$special#characters.d' reference.d
+
+    # -------------------------------------------------------------------------
+    if touch empty.c && $COMPILER -c -- empty.c 2>/dev/null; then
+        TEST "--"
+
+        $CCACHE_COMPILE -c -- test.c
+        expect_stat direct_cache_hit 0
+        expect_stat cache_miss 1
+
+        $CCACHE_COMPILE -c -- test.c
+        expect_stat direct_cache_hit 1
+        expect_stat cache_miss 1
+    fi
 }
index 8a227d0..c21969b 100644 (file)
@@ -443,7 +443,30 @@ fi
     expect_stat preprocessed_cache_hit 0
     expect_stat cache_miss 1
 
+    $CCACHE_COMPILE -c -Wp,-DFOO test.c
+    expect_stat direct_cache_hit 1
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+
+    $CCACHE_COMPILE -c -DFOO test.c
+    expect_stat direct_cache_hit 1
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 2
+
     $CCACHE_COMPILE -c -DFOO test.c
+    expect_stat direct_cache_hit 2
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 2
+
+    # -------------------------------------------------------------------------
+    TEST "-Wp,-U"
+
+    $CCACHE_COMPILE -c -Wp,-UFOO test.c
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+
+    $CCACHE_COMPILE -c -Wp,-UFOO test.c
     expect_stat direct_cache_hit 1
     expect_stat preprocessed_cache_hit 0
     expect_stat cache_miss 1
@@ -1204,7 +1227,7 @@ EOF
 
     manifest=`find $CCACHE_DIR -name '*M'`
     if [ -n "$manifest" ]; then
-        data="`$CCACHE --inspect $manifest | egrep '/dev/(stdout|tty|sda|hda'`"
+        data="`$CCACHE --inspect $manifest | grep -E '/dev/(stdout|tty|sda|hda'`"
         if [ -n "$data" ]; then
             test_failed "$manifest contained troublesome file(s): $data"
         fi
index 830ef8c..51a809f 100644 (file)
@@ -9,7 +9,7 @@ SUITE_inode_cache_PROBE() {
 
     touch test.c
     $CCACHE $COMPILER -c test.c
-    if [[ ! -f "${CCACHE_TEMPDIR}/inode-cache-32.v1" && ! -f "${CCACHE_TEMPDIR}/inode-cache-64.v1" ]]; then
+    if [[ ! -f "${CCACHE_TEMPDIR}/inode-cache-32.v2" && ! -f "${CCACHE_TEMPDIR}/inode-cache-64.v2" ]]; then
         local fs_type=$(stat -fLc %T "${CCACHE_DIR}")
         echo "inode cache not supported on ${fs_type}"
     fi
index c4a8c1d..e64afda 100644 (file)
@@ -11,7 +11,9 @@ SUITE_remote_file_SETUP() {
     unset CCACHE_NODIRECT
     export CCACHE_REMOTE_STORAGE="file:$PWD/remote"
 
-    generate_code 1 test.c
+    touch test.h
+    echo '#include "test.h"' >test.c
+    backdate test.h
 }
 
 SUITE_remote_file() {
@@ -195,11 +197,85 @@ SUITE_remote_file() {
     expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
 
     # -------------------------------------------------------------------------
+    TEST "Depend mode"
+
+    export CCACHE_DEPEND=1
+
+    # Compile and send result to local and remote storage.
+    $CCACHE_COMPILE -MMD -c test.c
+    expect_stat direct_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+    expect_stat local_storage_hit 0
+    expect_stat local_storage_miss 1
+    expect_stat local_storage_read_hit 0
+    expect_stat local_storage_read_miss 1 # only manifest
+    expect_stat local_storage_write 2 # result + manifest
+    expect_stat remote_storage_hit 0
+    expect_stat remote_storage_miss 1
+    expect_stat remote_storage_read_hit 0
+    expect_stat remote_storage_read_miss 1 # only manifest
+    expect_stat remote_storage_write 2 # result + manifest
+
+    # Get result from local storage.
+    $CCACHE_COMPILE -MMD -c test.c
+    expect_stat direct_cache_hit 1
+    expect_stat cache_miss 1
+    expect_stat local_storage_hit 1
+    expect_stat local_storage_miss 1
+    expect_stat local_storage_read_hit 2 # result + manifest
+    expect_stat local_storage_read_miss 1 # manifest
+    expect_stat local_storage_write 2 # result + manifest
+    expect_stat remote_storage_hit 0
+    expect_stat remote_storage_miss 1
+    expect_stat remote_storage_read_hit 0
+    expect_stat remote_storage_read_miss 1
+    expect_stat remote_storage_write 2
+
+    # Clear local storage.
+    $CCACHE -C >/dev/null
+
+    # Get result from remote storage, copying it to local storage.
+    # TERM=xterm-256color gdb --args $CCACHE_COMPILE -MMD -c test.c
+    $CCACHE_COMPILE -MMD -c test.c
+    expect_stat direct_cache_hit 2
+    expect_stat cache_miss 1
+    expect_stat local_storage_hit 1
+    expect_stat local_storage_miss 2
+    expect_stat local_storage_read_hit 2
+    expect_stat local_storage_read_miss 3
+    expect_stat local_storage_write 4
+    expect_stat remote_storage_hit 1
+    expect_stat remote_storage_miss 1
+    expect_stat remote_storage_read_hit 2 # result + manifest
+    expect_stat remote_storage_read_miss 1
+    expect_stat remote_storage_write 2 # result + manifest
+
+    # Remote cache read hit for the manifest but no manifest entry matches.
+    $CCACHE -C >/dev/null
+    echo 'int x;' >>test.h
+    backdate test.h
+    $CCACHE_COMPILE -MMD -c test.c
+    expect_stat direct_cache_hit 2
+    expect_stat cache_miss 2
+    expect_stat local_storage_hit 1
+    expect_stat local_storage_miss 3
+    expect_stat local_storage_read_hit 2
+    expect_stat local_storage_read_miss 4 # one manifest read miss
+    expect_stat local_storage_write 7 # download+store manifest, update+store manifest, write result
+    expect_stat remote_storage_hit 1
+    expect_stat remote_storage_miss 2
+    expect_stat remote_storage_read_hit 3
+    expect_stat remote_storage_read_miss 1 # original manifest didn't match -> no read
+    expect_stat remote_storage_write 4
+
+    # -------------------------------------------------------------------------
     TEST "umask"
 
     export CCACHE_UMASK=042
     CCACHE_REMOTE_STORAGE="file://$PWD/remote|umask=024"
-    rm -rf remote
+
+    # local -> remote, cache miss
     $CCACHE_COMPILE -c test.c
     expect_perm remote drwxr-x-wx # 777 & 024
     expect_perm remote/CACHEDIR.TAG -rw-r---w- # 666 & 024
@@ -207,12 +283,25 @@ SUITE_remote_file() {
     expect_perm "$(dirname "${result_file}")" drwx-wxr-x # 777 & 042
     expect_perm "${result_file}" -rw--w-r-- # 666 & 042
 
+    # local -> remote, local cache hit
     CCACHE_REMOTE_STORAGE="file://$PWD/remote|umask=026"
     $CCACHE -C >/dev/null
     rm -rf remote
     $CCACHE_COMPILE -c test.c
     expect_perm remote drwxr-x--x # 777 & 026
     expect_perm remote/CACHEDIR.TAG -rw-r----- # 666 & 026
+    result_file=$(find $CCACHE_DIR -name '*R')
+    expect_perm "$(dirname "${result_file}")" drwx-wxr-x # 777 & 042
+    expect_perm "${result_file}" -rw--w-r-- # 666 & 042
+
+    # remote -> local, remote cache hit
+    $CCACHE -C >/dev/null
+    $CCACHE_COMPILE -c test.c
+    expect_perm remote drwxr-x--x # 777 & 026
+    expect_perm remote/CACHEDIR.TAG -rw-r----- # 666 & 026
+    result_file=$(find $CCACHE_DIR -name '*R')
+    expect_perm "$(dirname "${result_file}")" drwx-wxr-x # 777 & 042
+    expect_perm "${result_file}" -rw--w-r-- # 666 & 042
 
     # -------------------------------------------------------------------------
     TEST "Sharding"
index 84351a0..dfe93d3 100644 (file)
@@ -1,5 +1,5 @@
 SUITE_remote_redis_PROBE() {
-    if ! $CCACHE --version | fgrep -q -- redis-storage &> /dev/null; then
+    if ! $CCACHE --version | grep -Fq -- redis-storage &> /dev/null; then
         echo "redis-storage not available"
         return
     fi
index 601e17c..1d709b5 100644 (file)
@@ -1,5 +1,5 @@
 SUITE_remote_redis_unix_PROBE() {
-    if ! $CCACHE --version | fgrep -q -- redis-storage &> /dev/null; then
+    if ! $CCACHE --version | grep -Fq -- redis-storage &> /dev/null; then
         echo "redis-storage not available"
         return
     fi
index de6a658..1b93b05 100644 (file)
@@ -1,13 +1,15 @@
 ---
-Checks:          '-*,
-                  readability-*,
-                  -readability-implicit-bool-conversion,
-                  -readability-function-cognitive-complexity,
-                  -readability-magic-numbers,
-                  -readability-else-after-return,
-                  -readability-named-parameter,
-                  -readability-qualified-auto,
-                  -readability-redundant-declaration'
+Checks: '
+  -*,
+  readability-*,
+  -readability-else-after-return,
+  -readability-function-cognitive-complexity,
+  -readability-implicit-bool-conversion,
+  -readability-magic-numbers,
+  -readability-named-parameter,
+  -readability-qualified-auto,
+  -readability-redundant-declaration,
+  '
 WarningsAsErrors: '*'
 # Only include headers directly in unittest.
 HeaderFilterRegex: 'unittest/[^/]*$'
@@ -28,4 +30,3 @@ CheckOptions:
   - key:             readability-function-size.VariableThreshold
     value:           999999
 ...
-
index b129d59..062961f 100644 (file)
@@ -20,6 +20,7 @@ set(
   test_hashutil.cpp
   test_storage_local_StatsFile.cpp
   test_storage_local_util.cpp
+  test_util_BitSet.cpp
   test_util_Bytes.cpp
   test_util_Duration.cpp
   test_util_LockFile.cpp
@@ -48,7 +49,7 @@ list(APPEND source_files ${headers})
 
 add_executable(unittest ${source_files})
 
-if(MSVC)
+if(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "^Clang$")
   # Turn off /Zc:preprocessor for this test because it triggers a bug in some older Windows 10 SDK headers.
   set_source_files_properties(test_Stat.cpp PROPERTIES COMPILE_FLAGS /Zc:preprocessor-)
 endif()
index 8ae2470..388213f 100644 (file)
@@ -139,21 +139,118 @@ TEST_CASE("Args::from_atfile")
     CHECK(args[6] == "seve\nth");
   }
 
-  SUBCASE("Only escape double quote and backslash in alternate format")
+  SUBCASE("Ignore single quote in MSVC format")
   {
-    util::write_file("at_file", "\"\\\"\\a\\ \\\\\\ \\b\\\"\"\\");
+    util::write_file("at_file", "'a b'");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 2);
+    CHECK(args[0] == "'a");
+    CHECK(args[1] == "b'");
+  }
+
+  SUBCASE("Backslash as directory separator in MSVC format")
+  {
+    util::write_file("at_file", R"("-DDIRSEP='A\B\C'")");
     args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
     CHECK(args.size() == 1);
-    CHECK(args[0] == "\"\\a\\ \\\\ \\b\"\\");
+    CHECK(args[0] == R"(-DDIRSEP='A\B\C')");
   }
 
-  SUBCASE("Ignore single quote in alternate format")
+  SUBCASE("Backslash before quote in MSVC format")
   {
-    util::write_file("at_file", "'a b'");
+    util::write_file("at_file", R"(/Fo"N.dir\Release\\")");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 1);
+    CHECK(args[0] == R"(/FoN.dir\Release\)");
+  }
+
+  SUBCASE("Arguments on multiple lines in MSVC format")
+  {
+    util::write_file("at_file", "a\nb");
     args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
     CHECK(args.size() == 2);
-    CHECK(args[0] == "'a");
-    CHECK(args[1] == "b'");
+    CHECK(args[0] == "a");
+    CHECK(args[1] == "b");
+  }
+
+  SUBCASE("Tricky quoting in MSVC format (#1247)")
+  {
+    util::write_file(
+      "at_file",
+      R"(\ \\ '\\' "\\" '"\\"' "'\\'" '''\\''' ''"\\"'' '"'\\'"' '""\\""' "''\\''" "'"\\"'" ""'\\'"" """\\""" )"
+      R"(\'\' '\'\'' "\'\'" ''\'\''' '"\'\'"' "'\'\''" ""\'\'"" '''\'\'''' ''"\'\'"'' '"'\'\''"' '""\'\'""' "''\'\'''" "'"\'\'"'" ""'\'\''"" """\'\'""" )"
+      R"(\"\" '\"\"' "\"\"" ''\"\"'' '"\"\""' "'\"\"'" ""\"\""" '''\"\"''' ''"\"\""'' '"'\"\"'"' '""\"\"""' "''\"\"''" "'"\"\""'" ""'\"\"'"" """\"\"""")");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 44);
+    CHECK(args[0] == R"(\)");
+    CHECK(args[1] == R"(\\)");
+    CHECK(args[2] == R"('\\')");
+    CHECK(args[3] == R"(\)");
+    CHECK(args[4] == R"('\')");
+    CHECK(args[5] == R"('\\')");
+    CHECK(args[6] == R"('''\\''')");
+    CHECK(args[7] == R"(''\'')");
+    CHECK(args[8] == R"(''\\'')");
+    CHECK(args[9] == R"('\')");
+    CHECK(args[10] == R"(''\\'')");
+    CHECK(args[11] == R"('\')");
+    CHECK(args[12] == R"('\\')");
+    CHECK(args[13] == R"("\")");
+    CHECK(args[14] == R"(\'\')");
+    CHECK(args[15] == R"('\'\'')");
+    CHECK(args[16] == R"(\'\')");
+    CHECK(args[17] == R"(''\'\''')");
+    CHECK(args[18] == R"('\'\'')");
+    CHECK(args[19] == R"('\'\'')");
+    CHECK(args[20] == R"(\'\')");
+    CHECK(args[21] == R"('''\'\'''')");
+    CHECK(args[22] == R"(''\'\''')");
+    CHECK(args[23] == R"(''\'\''')");
+    CHECK(args[24] == R"('\'\'')");
+    CHECK(args[25] == R"(''\'\''')");
+    CHECK(args[26] == R"('\'\'')");
+    CHECK(args[27] == R"('\'\'')");
+    CHECK(args[28] == R"("\'\'")");
+    CHECK(args[29] == R"("")");
+    CHECK(args[30] == R"('""')");
+    CHECK(args[31] == R"("")");
+    CHECK(args[32] == R"(''""'')");
+    CHECK(args[33] == R"('""')");
+    CHECK(args[34] == R"('""')");
+    CHECK(args[35] == R"("")");
+    CHECK(args[36] == R"('''""''')");
+    CHECK(args[37] == R"(''""'')");
+    CHECK(args[38] == R"(''""'')");
+    CHECK(args[39] == R"('""')");
+    CHECK(args[40] == R"(''""'')");
+    CHECK(args[41] == R"('""')");
+    CHECK(args[42] == R"('""')");
+    CHECK(args[43] == R"("""")");
+  }
+
+  SUBCASE("Quoting from Microsoft documentation in MSVC format")
+  {
+    // See
+    // https://learn.microsoft.com/en-us/previous-versions//17w5ykft(v=vs.85)?redirectedfrom=MSDN
+    util::write_file("at_file",
+                     R"("abc" d e )"
+                     R"(a\\\b d"e f"g h )"
+                     R"(a\\\"b c d )"
+                     R"(a\\\\"b c" d e)");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 12);
+    CHECK(args[0] == R"(abc)");
+    CHECK(args[1] == R"(d)");
+    CHECK(args[2] == R"(e)");
+    CHECK(args[3] == R"(a\\\b)");
+    CHECK(args[4] == R"(de fg)");
+    CHECK(args[5] == R"(h)");
+    CHECK(args[6] == R"(a\"b)");
+    CHECK(args[7] == R"(c)");
+    CHECK(args[8] == R"(d)");
+    CHECK(args[9] == R"(a\\b c)");
+    CHECK(args[10] == R"(d)");
+    CHECK(args[11] == R"(e)");
   }
 }
 
index 70a66dd..4a0b7b4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2011-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -59,11 +59,11 @@ TEST_CASE("Config: default values")
   CHECK(config.hash_dir());
   CHECK(config.ignore_headers_in_manifest().empty());
   CHECK(config.ignore_options().empty());
+  CHECK(config.inode_cache());
   CHECK_FALSE(config.keep_comments_cpp());
-  CHECK(config.limit_multiple() == Approx(0.8));
   CHECK(config.log_file().empty());
   CHECK(config.max_files() == 0);
-  CHECK(config.max_size() == static_cast<uint64_t>(5) * 1000 * 1000 * 1000);
+  CHECK(config.max_size() == static_cast<uint64_t>(5) * 1024 * 1024 * 1024);
   CHECK(config.msvc_dep_prefix() == "Note: including file:");
   CHECK(config.path().empty());
   CHECK_FALSE(config.pch_external_checksum());
@@ -119,7 +119,6 @@ TEST_CASE("Config::update_from_file")
     "ignore_headers_in_manifest = a:b/c\n"
     "ignore_options = -a=* -b\n"
     "keep_comments_cpp = true\n"
-    "limit_multiple = 1.0\n"
     "log_file = $USER${USER} \n"
     "max_files = 17\n"
     "max_size = 123M\n"
@@ -161,7 +160,6 @@ TEST_CASE("Config::update_from_file")
   CHECK(config.ignore_headers_in_manifest() == "a:b/c");
   CHECK(config.ignore_options() == "-a=* -b");
   CHECK(config.keep_comments_cpp());
-  CHECK(config.limit_multiple() == Approx(1.0));
   CHECK(config.log_file() == FMT("{0}{0}", user));
   CHECK(config.max_files() == 17);
   CHECK(config.max_size() == 123 * 1000 * 1000);
@@ -406,7 +404,6 @@ TEST_CASE("Config::visit_items")
     "ignore_options = -a=* -b\n"
     "inode_cache = false\n"
     "keep_comments_cpp = true\n"
-    "limit_multiple = 0.0\n"
     "log_file = lf\n"
     "max_files = 4711\n"
     "max_size = 98.7M\n"
@@ -468,10 +465,9 @@ TEST_CASE("Config::visit_items")
     "(test.conf) ignore_options = -a=* -b",
     "(test.conf) inode_cache = false",
     "(test.conf) keep_comments_cpp = true",
-    "(test.conf) limit_multiple = 0.0",
     "(test.conf) log_file = lf",
     "(test.conf) max_files = 4711",
-    "(test.conf) max_size = 98.7M",
+    "(test.conf) max_size = 98.7 MB",
     "(test.conf) msvc_dep_prefix = mdp",
     "(test.conf) namespace = ns",
     "(test.conf) path = p",
index b02adab..34bd003 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -55,7 +55,7 @@ bool
 put(InodeCache& inode_cache,
     const std::string& filename,
     const std::string& str,
-    int return_value)
+    HashSourceCodeResult return_value)
 {
   return inode_cache.put(filename,
                          InodeCache::ContentType::checked_for_temporal_macros,
@@ -77,16 +77,13 @@ TEST_CASE("Test disabled")
   InodeCache inode_cache(config, util::Duration(0));
 
   Digest digest;
-  int return_value;
 
-  CHECK(!inode_cache.get("a",
-                         InodeCache::ContentType::checked_for_temporal_macros,
-                         digest,
-                         &return_value));
+  CHECK(!inode_cache.get(
+    "a", InodeCache::ContentType::checked_for_temporal_macros, digest));
   CHECK(!inode_cache.put("a",
                          InodeCache::ContentType::checked_for_temporal_macros,
                          digest,
-                         return_value));
+                         HashSourceCodeResult()));
   CHECK(inode_cache.get_hits() == -1);
   CHECK(inode_cache.get_misses() == -1);
   CHECK(inode_cache.get_errors() == -1);
@@ -103,12 +100,9 @@ TEST_CASE("Test lookup nonexistent")
   util::write_file("a", "");
 
   Digest digest;
-  int return_value;
 
-  CHECK(!inode_cache.get("a",
-                         InodeCache::ContentType::checked_for_temporal_macros,
-                         digest,
-                         &return_value));
+  CHECK(!inode_cache.get(
+    "a", InodeCache::ContentType::checked_for_temporal_macros, digest));
   CHECK(inode_cache.get_hits() == 0);
   CHECK(inode_cache.get_misses() == 1);
   CHECK(inode_cache.get_errors() == 0);
@@ -124,39 +118,40 @@ TEST_CASE("Test put and lookup")
   InodeCache inode_cache(config, util::Duration(0));
   util::write_file("a", "a text");
 
-  CHECK(put(inode_cache, "a", "a text", 1));
+  HashSourceCodeResult result;
+  result.insert(HashSourceCode::found_date);
+  CHECK(put(inode_cache, "a", "a text", result));
 
   Digest digest;
-  int return_value;
-
-  CHECK(inode_cache.get("a",
-                        InodeCache::ContentType::checked_for_temporal_macros,
-                        digest,
-                        &return_value));
+  auto return_value = inode_cache.get(
+    "a", InodeCache::ContentType::checked_for_temporal_macros, digest);
+  REQUIRE(return_value);
   CHECK(digest == Hash().hash("a text").digest());
-  CHECK(return_value == 1);
+  CHECK(return_value->to_bitmask()
+        == static_cast<int>(HashSourceCode::found_date));
   CHECK(inode_cache.get_hits() == 1);
   CHECK(inode_cache.get_misses() == 0);
   CHECK(inode_cache.get_errors() == 0);
 
   util::write_file("a", "something else");
 
-  CHECK(!inode_cache.get("a",
-                         InodeCache::ContentType::checked_for_temporal_macros,
-                         digest,
-                         &return_value));
+  CHECK(!inode_cache.get(
+    "a", InodeCache::ContentType::checked_for_temporal_macros, digest));
   CHECK(inode_cache.get_hits() == 1);
   CHECK(inode_cache.get_misses() == 1);
   CHECK(inode_cache.get_errors() == 0);
 
-  CHECK(put(inode_cache, "a", "something else", 2));
+  CHECK(put(inode_cache,
+            "a",
+            "something else",
+            HashSourceCodeResult(HashSourceCode::found_time)));
 
-  CHECK(inode_cache.get("a",
-                        InodeCache::ContentType::checked_for_temporal_macros,
-                        digest,
-                        &return_value));
+  return_value = inode_cache.get(
+    "a", InodeCache::ContentType::checked_for_temporal_macros, digest);
+  REQUIRE(return_value);
   CHECK(digest == Hash().hash("something else").digest());
-  CHECK(return_value == 2);
+  CHECK(return_value->to_bitmask()
+        == static_cast<int>(HashSourceCode::found_time));
   CHECK(inode_cache.get_hits() == 2);
   CHECK(inode_cache.get_misses() == 1);
   CHECK(inode_cache.get_errors() == 0);
@@ -177,7 +172,7 @@ TEST_CASE("Drop file")
   CHECK(Stat::stat(inode_cache.get_file()));
   CHECK(inode_cache.drop());
   CHECK(!Stat::stat(inode_cache.get_file()));
-  CHECK(!inode_cache.drop());
+  CHECK(inode_cache.drop());
 }
 
 TEST_CASE("Test content type")
@@ -192,24 +187,30 @@ TEST_CASE("Test content type")
   Digest binary_digest = Hash().hash("binary").digest();
   Digest code_digest = Hash().hash("code").digest();
 
-  CHECK(inode_cache.put("a", InodeCache::ContentType::raw, binary_digest, 1));
-  CHECK(inode_cache.put(
-    "a", InodeCache::ContentType::checked_for_temporal_macros, code_digest, 2));
+  CHECK(inode_cache.put("a",
+                        InodeCache::ContentType::raw,
+                        binary_digest,
+                        HashSourceCodeResult(HashSourceCode::found_date)));
+  CHECK(inode_cache.put("a",
+                        InodeCache::ContentType::checked_for_temporal_macros,
+                        code_digest,
+                        HashSourceCodeResult(HashSourceCode::found_time)));
 
   Digest digest;
-  int return_value;
 
-  CHECK(
-    inode_cache.get("a", InodeCache::ContentType::raw, digest, &return_value));
+  auto return_value =
+    inode_cache.get("a", InodeCache::ContentType::raw, digest);
+  REQUIRE(return_value);
   CHECK(digest == binary_digest);
-  CHECK(return_value == 1);
+  CHECK(return_value->to_bitmask()
+        == static_cast<int>(HashSourceCode::found_date));
 
-  CHECK(inode_cache.get("a",
-                        InodeCache::ContentType::checked_for_temporal_macros,
-                        digest,
-                        &return_value));
+  return_value = inode_cache.get(
+    "a", InodeCache::ContentType::checked_for_temporal_macros, digest);
+  REQUIRE(return_value);
   CHECK(digest == code_digest);
-  CHECK(return_value == 2);
+  CHECK(return_value->to_bitmask()
+        == static_cast<int>(HashSourceCode::found_time));
 }
 
 TEST_SUITE_END();
index fbd0afe..ffdb680 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -198,6 +198,15 @@ TEST_CASE("Same i-node as")
   CHECK(!Stat::stat("nonexistent").same_inode_as(Stat::stat("nonexistent")));
 }
 
+TEST_CASE("Get path")
+{
+  TestContext test_context;
+
+  util::write_file("a", "");
+  CHECK(Stat::stat("a").path() == "a");
+  CHECK(Stat::stat("does_not_exist").path() == "does_not_exist");
+}
+
 TEST_CASE("Return values when file is missing")
 {
   auto stat = Stat::stat("does_not_exist");
@@ -256,7 +265,7 @@ TEST_CASE("Return values when file exists")
   CHECK(stat.mtime().sec() == last_write_time.tv_sec);
   CHECK(stat.mtime().nsec_decimal_part() == last_write_time.tv_nsec);
 
-  CHECK(stat.size_on_disk() == ((stat.size() + 1023) & ~1023));
+  CHECK(stat.size_on_disk() == ((stat.size() + 4095) & ~4095));
   CHECK(stat.file_attributes() == info.dwFileAttributes);
   CHECK(stat.reparse_tag() == 0);
 
index b71dfdb..fd02076 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -256,36 +256,6 @@ TEST_CASE("Util::format_base32hex")
   CHECK(Util::format_base32hex(input, 6) == "cpnmuoj1e8");
 }
 
-TEST_CASE("Util::format_human_readable_size")
-{
-  CHECK(Util::format_human_readable_size(0) == "0.0 kB");
-  CHECK(Util::format_human_readable_size(1) == "0.0 kB");
-  CHECK(Util::format_human_readable_size(49) == "0.0 kB");
-  CHECK(Util::format_human_readable_size(51) == "0.1 kB");
-  CHECK(Util::format_human_readable_size(949) == "0.9 kB");
-  CHECK(Util::format_human_readable_size(951) == "1.0 kB");
-  CHECK(Util::format_human_readable_size(499.7 * 1000) == "499.7 kB");
-  CHECK(Util::format_human_readable_size(1000 * 1000) == "1.0 MB");
-  CHECK(Util::format_human_readable_size(1234 * 1000) == "1.2 MB");
-  CHECK(Util::format_human_readable_size(438.5 * 1000 * 1000) == "438.5 MB");
-  CHECK(Util::format_human_readable_size(1000 * 1000 * 1000) == "1.0 GB");
-  CHECK(Util::format_human_readable_size(17.11 * 1000 * 1000 * 1000)
-        == "17.1 GB");
-}
-
-TEST_CASE("Util::format_parsable_size_with_suffix")
-{
-  CHECK(Util::format_parsable_size_with_suffix(0) == "0");
-  CHECK(Util::format_parsable_size_with_suffix(42 * 1000) == "42000");
-  CHECK(Util::format_parsable_size_with_suffix(1000 * 1000) == "1.0M");
-  CHECK(Util::format_parsable_size_with_suffix(1234 * 1000) == "1.2M");
-  CHECK(Util::format_parsable_size_with_suffix(438.5 * 1000 * 1000)
-        == "438.5M");
-  CHECK(Util::format_parsable_size_with_suffix(1000 * 1000 * 1000) == "1.0G");
-  CHECK(Util::format_parsable_size_with_suffix(17.11 * 1000 * 1000 * 1000)
-        == "17.1G");
-}
-
 TEST_CASE("Util::get_extension")
 {
   CHECK(Util::get_extension("") == "");
@@ -631,30 +601,6 @@ TEST_CASE("Util::parse_duration")
     "invalid suffix (supported: d (day) and s (second)): \"2\"");
 }
 
-TEST_CASE("Util::parse_size")
-{
-  CHECK(Util::parse_size("0") == 0);
-  CHECK(Util::parse_size("42") // Default suffix: G
-        == static_cast<uint64_t>(42) * 1000 * 1000 * 1000);
-  CHECK(Util::parse_size("78k") == 78 * 1000);
-  CHECK(Util::parse_size("78K") == 78 * 1000);
-  CHECK(Util::parse_size("1.1 M") == (int64_t(1.1 * 1000 * 1000)));
-  CHECK(Util::parse_size("438.55M") == (int64_t(438.55 * 1000 * 1000)));
-  CHECK(Util::parse_size("1 G") == 1 * 1000 * 1000 * 1000);
-  CHECK(Util::parse_size("2T")
-        == static_cast<uint64_t>(2) * 1000 * 1000 * 1000 * 1000);
-  CHECK(Util::parse_size("78 Ki") == 78 * 1024);
-  CHECK(Util::parse_size("1.1Mi") == (int64_t(1.1 * 1024 * 1024)));
-  CHECK(Util::parse_size("438.55 Mi") == (int64_t(438.55 * 1024 * 1024)));
-  CHECK(Util::parse_size("1Gi") == 1 * 1024 * 1024 * 1024);
-  CHECK(Util::parse_size("2 Ti")
-        == static_cast<uint64_t>(2) * 1024 * 1024 * 1024 * 1024);
-
-  CHECK_THROWS_WITH(Util::parse_size(""), "invalid size: \"\"");
-  CHECK_THROWS_WITH(Util::parse_size("x"), "invalid size: \"x\"");
-  CHECK_THROWS_WITH(Util::parse_size("10x"), "invalid size: \"10x\"");
-}
-
 TEST_CASE("Util::remove_extension")
 {
   CHECK(Util::remove_extension("") == "");
index 1a59f6a..dc7e935 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 #include "TestUtil.hpp"
 #include "argprocessing.hpp"
 
+#include <Util.hpp>
 #include <core/Statistic.hpp>
 #include <core/wincompat.hpp>
 #include <util/file.hpp>
+#include <util/string.hpp>
 
 #include "third_party/doctest.h"
 
@@ -59,7 +61,9 @@ get_posix_path(const std::string& path)
   std::string posix;
 
   // /-escape volume.
-  if (path[0] >= 'A' && path[0] <= 'Z' && path[1] == ':') {
+  if (path[1] == ':'
+      && ((path[0] >= 'A' && path[0] <= 'Z')
+          || (path[0] >= 'a' && path[0] <= 'z'))) {
     posix = "/" + path;
   } else {
     posix = path;
@@ -565,6 +569,42 @@ TEST_CASE("cuda_option_file")
   CHECK(result.compiler_args.to_string() == "nvcc -g -Wall -DX -c");
 }
 
+TEST_CASE("nvcc_warning_flags_short")
+{
+  // With -Werror. This should conflict with host's -Werror flag.
+  TestContext test_context;
+  Context ctx;
+  ctx.config.set_compiler_type(CompilerType::nvcc);
+  ctx.orig_args =
+    Args::from_string("nvcc -Werror all-warnings -Xcompiler -Werror -c foo.cu");
+  util::write_file("foo.cu", "");
+  const ProcessArgsResult result = process_args(ctx);
+
+  CHECK(!result.error);
+  CHECK(result.preprocessor_args.to_string() == "nvcc -Xcompiler -Werror");
+  CHECK(result.extra_args_to_hash.to_string() == "-Werror all-warnings");
+  CHECK(result.compiler_args.to_string()
+        == "nvcc -Werror all-warnings -Xcompiler -Werror -c");
+}
+
+TEST_CASE("nvcc_warning_flags_long")
+{
+  // With --Werror. This shouldn't conflict with host's -Werror flag.
+  TestContext test_context;
+  Context ctx;
+  ctx.config.set_compiler_type(CompilerType::nvcc);
+  ctx.orig_args = Args::from_string(
+    "nvcc --Werror all-warnings -Xcompiler -Werror -c foo.cu");
+  util::write_file("foo.cu", "");
+  const ProcessArgsResult result = process_args(ctx);
+
+  CHECK(!result.error);
+  CHECK(result.preprocessor_args.to_string() == "nvcc -Xcompiler -Werror");
+  CHECK(result.extra_args_to_hash.to_string() == "--Werror all-warnings");
+  CHECK(result.compiler_args.to_string()
+        == "nvcc --Werror all-warnings -Xcompiler -Werror -c");
+}
+
 TEST_CASE("-Xclang")
 {
   TestContext test_context;
@@ -660,7 +700,7 @@ TEST_CASE("-x")
 
   SUBCASE("UNKNOWN -x option (uppercase)")
   {
-    ctx.orig_args = Args::from_string("gcc -x UNSUPPORTED_LANGUGAGE -c foo.c");
+    ctx.orig_args = Args::from_string("gcc -x UNSUPPORTED_LANGUAGE -c foo.c");
     const ProcessArgsResult result = process_args(ctx);
     CHECK(result.error == Statistic::unsupported_source_language);
     CHECK(ctx.args_info.actual_language == "");
@@ -675,4 +715,97 @@ TEST_CASE("-x")
   }
 }
 
+// On macOS ctx.actual_cwd() typically starts with /Users which clashes with
+// MSVC's /U option, so disable the test case there. This will be possible to
+// improve when/if a compiler abstraction is introduced (issue #956).
+TEST_CASE("MSVC options"
+          * doctest::skip(util::starts_with(Util::get_actual_cwd(), "/U")))
+{
+  TestContext test_context;
+  Context ctx;
+  ctx.config.set_compiler_type(CompilerType::msvc);
+
+  util::write_file("foo.c", "");
+
+  ctx.orig_args = Args::from_string(
+    FMT("cl.exe /Fobar.obj /c {}/foo.c /foobar", ctx.actual_cwd));
+  const ProcessArgsResult result = process_args(ctx);
+  CHECK(!result.error);
+  CHECK(result.preprocessor_args.to_string() == "cl.exe /foobar");
+  CHECK(result.compiler_args.to_string() == "cl.exe /foobar -c");
+}
+
+TEST_CASE("MSVC debug information format options")
+{
+  TestContext test_context;
+  Context ctx;
+  ctx.config.set_compiler_type(CompilerType::msvc);
+  util::write_file("foo.c", "");
+
+  SUBCASE("Only /Z7")
+  {
+    ctx.orig_args = Args::from_string("cl.exe /c foo.c /Z7");
+    const ProcessArgsResult result = process_args(ctx);
+    REQUIRE(!result.error);
+    CHECK(result.preprocessor_args.to_string() == "cl.exe /Z7");
+    CHECK(result.compiler_args.to_string() == "cl.exe /Z7 -c");
+  }
+
+  SUBCASE("Only /Zi")
+  {
+    ctx.orig_args = Args::from_string("cl.exe /c foo.c /Zi");
+    const ProcessArgsResult result = process_args(ctx);
+    CHECK(result.error == Statistic::unsupported_compiler_option);
+  }
+
+  SUBCASE("Only /ZI")
+  {
+    ctx.orig_args = Args::from_string("cl.exe /c foo.c /ZI");
+    const ProcessArgsResult result = process_args(ctx);
+    CHECK(result.error == Statistic::unsupported_compiler_option);
+  }
+
+  SUBCASE("/Z7 + /Zi")
+  {
+    ctx.orig_args = Args::from_string("cl.exe /Z7 /c foo.c /Zi");
+    const ProcessArgsResult result = process_args(ctx);
+    CHECK(result.error == Statistic::unsupported_compiler_option);
+  }
+
+  SUBCASE("/Zi + /Z7")
+  {
+    ctx.orig_args = Args::from_string("cl.exe /Zi /c foo.c /Z7");
+    const ProcessArgsResult result = process_args(ctx);
+    REQUIRE(!result.error);
+    CHECK(result.preprocessor_args.to_string() == "cl.exe /Zi /Z7");
+    CHECK(result.compiler_args.to_string() == "cl.exe /Zi /Z7 -c");
+  }
+}
+
+// Check that clang-cl debug information is parsed different,
+// since for clang-cl /Zi and /Z7 is the same!
+TEST_CASE("ClangCL Debug information options")
+{
+  TestContext test_context;
+  Context ctx;
+  ctx.config.set_compiler_type(CompilerType::clang_cl);
+  util::write_file("foo.c", "");
+
+  SUBCASE("/Z7")
+  {
+    ctx.orig_args = Args::from_string("clang-cl.exe /c foo.c /Z7");
+    const ProcessArgsResult result = process_args(ctx);
+    REQUIRE(!result.error);
+    CHECK(result.preprocessor_args.to_string() == "clang-cl.exe /Z7");
+  }
+
+  SUBCASE("/Zi")
+  {
+    ctx.orig_args = Args::from_string("clang-cl.exe /c foo.c /Zi");
+    const ProcessArgsResult result = process_args(ctx);
+    REQUIRE(!result.error);
+    CHECK(result.preprocessor_args.to_string() == "clang-cl.exe /Zi");
+  }
+}
+
 TEST_SUITE_END();
index 30f3bdd..61671f8 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -38,7 +38,8 @@ TEST_SUITE_BEGIN("ccache");
 
 // Wraps find_compiler in a test friendly interface.
 static std::string
-helper(const char* args,
+helper(bool masquerading_as_compiler,
+       const char* args,
        const char* config_compiler,
        const char* find_executable_return_string = nullptr)
 {
@@ -51,10 +52,60 @@ helper(const char* args,
   Context ctx;
   ctx.config.set_compiler(config_compiler);
   ctx.orig_args = Args::from_string(args);
-  find_compiler(ctx, find_executable_stub);
+  find_compiler(ctx, find_executable_stub, masquerading_as_compiler);
   return ctx.orig_args.to_string();
 }
 
+TEST_CASE("split_argv")
+{
+  ArgvParts argv_parts;
+
+  SUBCASE("empty")
+  {
+    argv_parts = split_argv(0, nullptr);
+    CHECK(argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings.empty());
+    CHECK(argv_parts.compiler_and_args.empty());
+  }
+
+  SUBCASE("ccache")
+  {
+    const char* const argv[] = {"ccache"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings.empty());
+    CHECK(argv_parts.compiler_and_args.empty());
+  }
+
+  SUBCASE("normal compilation")
+  {
+    const char* const argv[] = {"ccache", "gcc", "-c", "test.c"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings.empty());
+    CHECK(argv_parts.compiler_and_args == Args::from_string("gcc -c test.c"));
+  }
+
+  SUBCASE("only config options")
+  {
+    const char* const argv[] = {"ccache", "foo=bar"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings == std::vector<std::string>{"foo=bar"});
+    CHECK(argv_parts.compiler_and_args.empty());
+  }
+
+  SUBCASE("compilation with config options")
+  {
+    const char* const argv[] = {"ccache", "a=b", "c = d", "/usr/bin/gcc"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings
+          == std::vector<std::string>{"a=b", "c = d"});
+    CHECK(argv_parts.compiler_and_args == Args::from_string("/usr/bin/gcc"));
+  }
+}
+
 TEST_CASE("find_compiler")
 {
   SUBCASE("no config")
@@ -62,60 +113,27 @@ TEST_CASE("find_compiler")
     // In case the first parameter is gcc it must be a link to ccache, so
     // find_compiler should call find_executable to locate the next best "gcc"
     // and return that value.
-    CHECK(helper("gcc", "") == "resolved_gcc");
-    CHECK(helper("relative/gcc", "") == "resolved_gcc");
-    CHECK(helper("/absolute/gcc", "") == "resolved_gcc");
+    CHECK(helper(true, "gcc", "") == "resolved_gcc");
+    CHECK(helper(true, "relative/gcc", "") == "resolved_gcc");
+    CHECK(helper(true, "/absolute/gcc", "") == "resolved_gcc");
 
     // In case the first parameter is ccache, resolve the second parameter to
     // the real compiler unless it's a relative or absolute path.
-    CHECK(helper("ccache gcc", "") == "resolved_gcc");
-    CHECK(helper("ccache rel/gcc", "") == "rel/gcc");
-    CHECK(helper("ccache /abs/gcc", "") == "/abs/gcc");
-
-    CHECK(helper("rel/ccache gcc", "") == "resolved_gcc");
-    CHECK(helper("rel/ccache rel/gcc", "") == "rel/gcc");
-    CHECK(helper("rel/ccache /abs/gcc", "") == "/abs/gcc");
-
-    CHECK(helper("/abs/ccache gcc", "") == "resolved_gcc");
-    CHECK(helper("/abs/ccache rel/gcc", "") == "rel/gcc");
-    CHECK(helper("/abs/ccache /abs/gcc", "") == "/abs/gcc");
+    CHECK(helper(false, "gcc", "") == "resolved_gcc");
+    CHECK(helper(false, "rel/gcc", "") == "rel/gcc");
+    CHECK(helper(false, "/abs/gcc", "") == "/abs/gcc");
 
     // If gcc points back to ccache throw, unless either ccache or gcc is a
     // relative or absolute path.
-    CHECK_THROWS(helper("ccache gcc", "", "ccache"));
-    CHECK(helper("ccache rel/gcc", "", "ccache") == "rel/gcc");
-    CHECK(helper("ccache /abs/gcc", "", "ccache") == "/abs/gcc");
-
-    CHECK_THROWS(helper("rel/ccache gcc", "", "ccache"));
-    CHECK(helper("rel/ccache rel/gcc", "", "ccache") == "rel/gcc");
-    CHECK(helper("rel/ccache /a/gcc", "", "ccache") == "/a/gcc");
-
-    CHECK_THROWS(helper("/abs/ccache gcc", "", "ccache"));
-    CHECK(helper("/abs/ccache rel/gcc", "", "ccache") == "rel/gcc");
-    CHECK(helper("/abs/ccache /a/gcc", "", "ccache") == "/a/gcc");
+    CHECK_THROWS(helper(false, "gcc", "", "ccache"));
+    CHECK(helper(false, "rel/gcc", "", "ccache") == "rel/gcc");
+    CHECK(helper(false, "/abs/gcc", "", "ccache") == "/abs/gcc");
 
     // If compiler is not found then throw, unless the compiler has a relative
     // or absolute path.
-    CHECK_THROWS(helper("ccache gcc", "", ""));
-    CHECK(helper("ccache rel/gcc", "", "") == "rel/gcc");
-    CHECK(helper("ccache /abs/gcc", "", "") == "/abs/gcc");
-
-    CHECK_THROWS(helper("rel/ccache gcc", "", ""));
-    CHECK(helper("rel/ccache rel/gcc", "", "") == "rel/gcc");
-    CHECK(helper("rel/ccache /abs/gcc", "", "") == "/abs/gcc");
-
-    CHECK_THROWS(helper("/abs/ccache gcc", "", ""));
-    CHECK(helper("/abs/ccache rel/gcc", "", "") == "rel/gcc");
-    CHECK(helper("/abs/ccache /abs/gcc", "", "") == "/abs/gcc");
-  }
-
-  SUBCASE("double ccache")
-  {
-    // E.g. due to some suboptimal setup, scripts etc. Source:
-    // https://github.com/ccache/ccache/issues/686
-    CHECK(helper("ccache gcc", "") == "resolved_gcc");
-    CHECK(helper("ccache ccache gcc", "") == "resolved_gcc");
-    CHECK(helper("ccache ccache-1.2.3 ccache gcc", "") == "resolved_gcc");
+    CHECK_THROWS(helper(false, "gcc", "", ""));
+    CHECK(helper(false, "rel/gcc", "", "") == "rel/gcc");
+    CHECK(helper(false, "/abs/gcc", "", "") == "/abs/gcc");
   }
 
   SUBCASE("config")
@@ -123,37 +141,23 @@ TEST_CASE("find_compiler")
     // In case the first parameter is gcc it must be a link to ccache so use
     // config value instead. Don't resolve config if it's a relative or absolute
     // path.
-    CHECK(helper("gcc", "config") == "resolved_config");
-    CHECK(helper("gcc", "rel/config") == "rel/config");
-    CHECK(helper("gcc", "/abs/config") == "/abs/config");
-    CHECK(helper("rel/gcc", "config") == "resolved_config");
-    CHECK(helper("rel/gcc", "rel/config") == "rel/config");
-    CHECK(helper("rel/gcc", "/abs/config") == "/abs/config");
-    CHECK(helper("/abs/gcc", "config") == "resolved_config");
-    CHECK(helper("/abs/gcc", "rel/config") == "rel/config");
-    CHECK(helper("/abs/gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(true, "gcc", "config") == "resolved_config");
+    CHECK(helper(true, "gcc", "rel/config") == "rel/config");
+    CHECK(helper(true, "gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(true, "rel/gcc", "config") == "resolved_config");
+    CHECK(helper(true, "rel/gcc", "rel/config") == "rel/config");
+    CHECK(helper(true, "rel/gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(true, "/abs/gcc", "config") == "resolved_config");
+    CHECK(helper(true, "/abs/gcc", "rel/config") == "rel/config");
+    CHECK(helper(true, "/abs/gcc", "/abs/config") == "/abs/config");
 
     // In case the first parameter is ccache, use the configuration value. Don't
     // resolve configuration value if it's a relative or absolute path.
-    CHECK(helper("ccache gcc", "config") == "resolved_config");
-    CHECK(helper("ccache gcc", "rel/config") == "rel/config");
-    CHECK(helper("ccache gcc", "/abs/config") == "/abs/config");
-    CHECK(helper("ccache rel/gcc", "config") == "resolved_config");
-    CHECK(helper("ccache /abs/gcc", "config") == "resolved_config");
-
-    // Same as above with relative path to ccache.
-    CHECK(helper("r/ccache gcc", "conf") == "resolved_conf");
-    CHECK(helper("r/ccache gcc", "rel/conf") == "rel/conf");
-    CHECK(helper("r/ccache gcc", "/abs/conf") == "/abs/conf");
-    CHECK(helper("r/ccache rel/gcc", "conf") == "resolved_conf");
-    CHECK(helper("r/ccache /abs/gcc", "conf") == "resolved_conf");
-
-    // Same as above with absolute path to ccache.
-    CHECK(helper("/a/ccache gcc", "conf") == "resolved_conf");
-    CHECK(helper("/a/ccache gcc", "rel/conf") == "rel/conf");
-    CHECK(helper("/a/ccache gcc", "/a/conf") == "/a/conf");
-    CHECK(helper("/a/ccache rel/gcc", "conf") == "resolved_conf");
-    CHECK(helper("/a/ccache /abs/gcc", "conf") == "resolved_conf");
+    CHECK(helper(false, "gcc", "config") == "resolved_config");
+    CHECK(helper(false, "gcc", "rel/config") == "rel/config");
+    CHECK(helper(false, "gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(false, "rel/gcc", "config") == "resolved_config");
+    CHECK(helper(false, "/abs/gcc", "config") == "resolved_config");
   }
 }
 
index e6f6262..bb4522c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -167,67 +167,84 @@ TEST_CASE("check_for_temporal_macros")
     "#define alphabet abcdefghijklmnopqrstuvwxyz\n"
     "__DATE__";
 
-  CHECK(check_for_temporal_macros(time_start));
-  CHECK(!check_for_temporal_macros(time_start.substr(1)));
-
-  CHECK(check_for_temporal_macros(time_middle.substr(0)));
-  CHECK(check_for_temporal_macros(time_middle.substr(1)));
-  CHECK(check_for_temporal_macros(time_middle.substr(2)));
-  CHECK(check_for_temporal_macros(time_middle.substr(3)));
-  CHECK(check_for_temporal_macros(time_middle.substr(4)));
-  CHECK(check_for_temporal_macros(time_middle.substr(5)));
-  CHECK(check_for_temporal_macros(time_middle.substr(6)));
-  CHECK(check_for_temporal_macros(time_middle.substr(7)));
-
-  CHECK(check_for_temporal_macros(time_end));
-  CHECK(check_for_temporal_macros(time_end.substr(time_end.length() - 8)));
-  CHECK(!check_for_temporal_macros(time_end.substr(time_end.length() - 7)));
-
-  CHECK(check_for_temporal_macros(date_start));
-  CHECK(!check_for_temporal_macros(date_start.substr(1)));
-
-  CHECK(check_for_temporal_macros(date_middle.substr(0)));
-  CHECK(check_for_temporal_macros(date_middle.substr(1)));
-  CHECK(check_for_temporal_macros(date_middle.substr(2)));
-  CHECK(check_for_temporal_macros(date_middle.substr(3)));
-  CHECK(check_for_temporal_macros(date_middle.substr(4)));
-  CHECK(check_for_temporal_macros(date_middle.substr(5)));
-  CHECK(check_for_temporal_macros(date_middle.substr(6)));
-  CHECK(check_for_temporal_macros(date_middle.substr(7)));
-
-  CHECK(check_for_temporal_macros(date_end));
-  CHECK(check_for_temporal_macros(date_end.substr(date_end.length() - 8)));
-  CHECK(!check_for_temporal_macros(date_end.substr(date_end.length() - 7)));
-
-  CHECK(check_for_temporal_macros(timestamp_start));
-  CHECK(!check_for_temporal_macros(timestamp_start.substr(1)));
-
-  CHECK(check_for_temporal_macros(timestamp_middle));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(1)));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(2)));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(3)));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(4)));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(5)));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(6)));
-  CHECK(check_for_temporal_macros(timestamp_middle.substr(7)));
-
-  CHECK(check_for_temporal_macros(timestamp_end));
-  CHECK(check_for_temporal_macros(
-    timestamp_end.substr(timestamp_end.length() - 13)));
-  CHECK(!check_for_temporal_macros(
-    timestamp_end.substr(timestamp_end.length() - 12)));
-
-  CHECK(!check_for_temporal_macros(no_temporal.substr(0)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(1)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(2)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(3)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(4)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(5)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(6)));
-  CHECK(!check_for_temporal_macros(no_temporal.substr(7)));
-
-  for (size_t i = 0; i < sizeof(temporal_at_avx_boundary) - 8; ++i) {
-    CHECK(check_for_temporal_macros(temporal_at_avx_boundary.substr(i)));
+  const std::string_view no_temporal_at_avx_boundary =
+    "#define alphabet abcdefghijklmnopqrstuvwxyz\n"
+    "a__DATE__";
+
+  auto check = check_for_temporal_macros;
+
+  CHECK(check(time_start).contains(HashSourceCode::found_time));
+  CHECK(check(time_start.substr(1)).empty());
+
+  CHECK(check(time_middle.substr(0)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(1)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(2)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(3)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(4)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(5)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(6)).contains(HashSourceCode::found_time));
+  CHECK(check(time_middle.substr(7)).contains(HashSourceCode::found_time));
+
+  CHECK(check(time_end).contains(HashSourceCode::found_time));
+  CHECK(check(time_end.substr(time_end.length() - 8))
+          .contains(HashSourceCode::found_time));
+  CHECK(check(time_end.substr(time_end.length() - 7)).empty());
+
+  CHECK(check(date_start).contains(HashSourceCode::found_date));
+  CHECK(check(date_start.substr(1)).empty());
+
+  CHECK(check(date_middle.substr(0)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(1)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(2)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(3)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(4)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(5)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(6)).contains(HashSourceCode::found_date));
+  CHECK(check(date_middle.substr(7)).contains(HashSourceCode::found_date));
+
+  CHECK(check(date_end).contains(HashSourceCode::found_date));
+  CHECK(check(date_end.substr(date_end.length() - 8))
+          .contains(HashSourceCode::found_date));
+  CHECK(check(date_end.substr(date_end.length() - 7)).empty());
+
+  CHECK(check(timestamp_start).contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_start.substr(1)).empty());
+
+  CHECK(check(timestamp_middle).contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(1))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(2))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(3))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(4))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(5))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(6))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_middle.substr(7))
+          .contains(HashSourceCode::found_timestamp));
+
+  CHECK(check(timestamp_end).contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_end.substr(timestamp_end.length() - 13))
+          .contains(HashSourceCode::found_timestamp));
+  CHECK(check(timestamp_end.substr(timestamp_end.length() - 12)).empty());
+
+  CHECK(check(no_temporal.substr(0)).empty());
+  CHECK(check(no_temporal.substr(1)).empty());
+  CHECK(check(no_temporal.substr(2)).empty());
+  CHECK(check(no_temporal.substr(3)).empty());
+  CHECK(check(no_temporal.substr(4)).empty());
+  CHECK(check(no_temporal.substr(5)).empty());
+  CHECK(check(no_temporal.substr(6)).empty());
+  CHECK(check(no_temporal.substr(7)).empty());
+
+  for (size_t i = 0; i < temporal_at_avx_boundary.size() - 8; ++i) {
+    CHECK(!check(temporal_at_avx_boundary.substr(i)).empty());
+  }
+  for (size_t i = 0; i < no_temporal_at_avx_boundary.size() - 8; ++i) {
+    CHECK(check(no_temporal_at_avx_boundary.substr(i)).empty());
   }
 }
 
index 3ed23c5..40b3e6f 100644 (file)
@@ -19,6 +19,7 @@
 #include "TestUtil.hpp"
 
 #include <Util.hpp>
+#include <fmtmacros.hpp>
 #include <storage/local/util.hpp>
 #include <util/file.hpp>
 
@@ -41,36 +42,19 @@ os_path(std::string path)
 
 TEST_SUITE_BEGIN("storage::local::util");
 
-TEST_CASE("storage::local::for_each_level_1_subdir")
+TEST_CASE("storage::local::for_each_cache_subdir")
 {
-  std::vector<std::string> actual;
-  storage::local::for_each_level_1_subdir(
-    "cache_dir",
-    [&](const auto& subdir, const auto&) { actual.push_back(subdir); },
-    [](double) {});
-
-  std::vector<std::string> expected = {
-    "cache_dir/0",
-    "cache_dir/1",
-    "cache_dir/2",
-    "cache_dir/3",
-    "cache_dir/4",
-    "cache_dir/5",
-    "cache_dir/6",
-    "cache_dir/7",
-    "cache_dir/8",
-    "cache_dir/9",
-    "cache_dir/a",
-    "cache_dir/b",
-    "cache_dir/c",
-    "cache_dir/d",
-    "cache_dir/e",
-    "cache_dir/f",
-  };
+  std::vector<uint8_t> actual;
+  storage::local::for_each_cache_subdir(
+    [](double) {},
+    [&](uint8_t index, const auto&) { actual.push_back(index); });
+
+  std::vector<uint8_t> expected = {
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
   CHECK(actual == expected);
 }
 
-TEST_CASE("storage::local::get_level_1_files")
+TEST_CASE("storage::local::get_cache_dir_files")
 {
   TestContext test_context;
 
@@ -83,23 +67,21 @@ TEST_CASE("storage::local::get_level_1_files")
   util::write_file("0/1/file_c", "12");
   util::write_file("0/f/c/file_d", "123");
 
-  auto null_receiver = [](double) {};
-
   SUBCASE("nonexistent subdirectory")
   {
-    const auto files = storage::local::get_level_1_files("2", null_receiver);
+    const auto files = storage::local::get_cache_dir_files("2");
     CHECK(files.empty());
   }
 
   SUBCASE("empty subdirectory")
   {
-    const auto files = storage::local::get_level_1_files("e", null_receiver);
+    const auto files = storage::local::get_cache_dir_files("e");
     CHECK(files.empty());
   }
 
   SUBCASE("simple case")
   {
-    auto files = storage::local::get_level_1_files("0", null_receiver);
+    auto files = storage::local::get_cache_dir_files("0");
     REQUIRE(files.size() == 4);
 
     // Files within a level are in arbitrary order, sort them to be able to
@@ -109,13 +91,13 @@ TEST_CASE("storage::local::get_level_1_files")
     });
 
     CHECK(files[0].path() == os_path("0/1/file_b"));
-    CHECK(files[0].lstat().size() == 1);
+    CHECK(files[0].size() == 1);
     CHECK(files[1].path() == os_path("0/1/file_c"));
-    CHECK(files[1].lstat().size() == 2);
+    CHECK(files[1].size() == 2);
     CHECK(files[2].path() == os_path("0/f/c/file_d"));
-    CHECK(files[2].lstat().size() == 3);
+    CHECK(files[2].size() == 3);
     CHECK(files[3].path() == os_path("0/file_a"));
-    CHECK(files[3].lstat().size() == 0);
+    CHECK(files[3].size() == 0);
   }
 }
 
diff --git a/unittest/test_util_BitSet.cpp b/unittest/test_util_BitSet.cpp
new file mode 100644 (file)
index 0000000..3130794
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "../src/util/BitSet.hpp"
+#include "TestUtil.hpp"
+
+#include <third_party/doctest.h>
+
+TEST_SUITE_BEGIN("BitSet");
+
+TEST_CASE("Operations")
+{
+  enum class Test { A = 1U << 0, B = 1U << 1, C = 1U << 2 };
+
+  util::BitSet<Test> es;
+  CHECK(es.empty());
+  CHECK(!es.contains(Test::A));
+  CHECK(!es.contains(Test::B));
+  CHECK(es.to_bitmask() == 0);
+
+  es.insert(Test::A);
+  CHECK(!es.empty());
+  CHECK(es.contains(Test::A));
+  CHECK(!es.contains(Test::B));
+  CHECK(es.to_bitmask() == 1);
+
+  es.insert(Test::B);
+  CHECK(!es.empty());
+  CHECK(es.contains(Test::A));
+  CHECK(es.contains(Test::B));
+  CHECK(es.to_bitmask() == 3);
+
+  util::BitSet<Test> es2(es);
+  CHECK(!es2.empty());
+  CHECK(es2.contains(Test::A));
+  CHECK(es2.contains(Test::B));
+  CHECK(es2.to_bitmask() == 3);
+
+  es.erase(Test::A);
+  CHECK(!es.empty());
+  CHECK(!es.contains(Test::A));
+  CHECK(es.contains(Test::B));
+  CHECK(es.to_bitmask() == 2);
+
+  util::BitSet<Test> es3(Test::C);
+  es3.insert(es2);
+  CHECK(!es3.empty());
+  CHECK(es3.contains(Test::A));
+  CHECK(es3.contains(Test::B));
+  CHECK(es3.contains(Test::C));
+  CHECK(es3.to_bitmask() == 7);
+
+  es3.erase(Test::B);
+  CHECK(!es3.empty());
+  CHECK(es3.contains(Test::A));
+  CHECK(!es3.contains(Test::B));
+  CHECK(es3.contains(Test::C));
+  CHECK(es3.to_bitmask() == 5);
+}
+
+TEST_SUITE_END();
index ff3153c..c5c30d9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -40,68 +40,44 @@ TEST_CASE("Acquire and release short-lived lock file")
 {
   TestContext test_context;
 
-  util::ShortLivedLockFile lock_file("test");
+  util::LockFile lock("test");
   {
-    CHECK(!lock_file.acquired());
+    CHECK(!lock.acquired());
     CHECK(!Stat::lstat("test.lock"));
     CHECK(!Stat::lstat("test.alive"));
 
-    util::LockFileGuard lock(lock_file);
-    CHECK(lock_file.acquired());
+    CHECK(lock.acquire());
     CHECK(lock.acquired());
-    CHECK(!Stat::lstat("test.alive"));
     const auto st = Stat::lstat("test.lock");
     CHECK(st);
 #ifndef _WIN32
+    CHECK(Stat::lstat("test.alive"));
     CHECK(st.is_symlink());
 #else
     CHECK(st.is_regular());
 #endif
   }
 
-  CHECK(!lock_file.acquired());
+  lock.release();
+  lock.release();
+  CHECK(!lock.acquired());
   CHECK(!Stat::lstat("test.lock"));
   CHECK(!Stat::lstat("test.alive"));
 }
 
-TEST_CASE("Non-blocking short-lived lock")
-{
-  TestContext test_context;
-
-  util::ShortLivedLockFile lock_file_1("test");
-  CHECK(!lock_file_1.acquired());
-
-  util::ShortLivedLockFile lock_file_2("test");
-  CHECK(!lock_file_2.acquired());
-
-  CHECK(lock_file_1.try_acquire());
-  CHECK(lock_file_1.acquired());
-
-  CHECK(!lock_file_2.try_acquire());
-  CHECK(lock_file_1.acquired());
-  CHECK(!lock_file_2.acquired());
-
-  lock_file_2.release();
-  CHECK(lock_file_1.acquired());
-  CHECK(!lock_file_2.acquired());
-
-  lock_file_1.release();
-  CHECK(!lock_file_1.acquired());
-  CHECK(!lock_file_2.acquired());
-}
-
 TEST_CASE("Acquire and release long-lived lock file")
 {
   TestContext test_context;
 
-  util::LongLivedLockFile lock_file("test");
+  util::LongLivedLockFileManager lock_manager;
+  util::LockFile lock("test");
+  lock.make_long_lived(lock_manager);
   {
-    CHECK(!lock_file.acquired());
+    CHECK(!lock.acquired());
     CHECK(!Stat::lstat("test.lock"));
     CHECK(!Stat::lstat("test.alive"));
 
-    util::LockFileGuard lock(lock_file);
-    CHECK(lock_file.acquired());
+    CHECK(lock.acquire());
     CHECK(lock.acquired());
 #ifndef _WIN32
     CHECK(Stat::lstat("test.alive"));
@@ -115,7 +91,9 @@ TEST_CASE("Acquire and release long-lived lock file")
 #endif
   }
 
-  CHECK(!lock_file.acquired());
+  lock.release();
+  lock.release();
+  CHECK(!lock.acquired());
   CHECK(!Stat::lstat("test.lock"));
   CHECK(!Stat::lstat("test.alive"));
 }
@@ -124,9 +102,10 @@ TEST_CASE("LockFile creates missing directories")
 {
   TestContext test_context;
 
-  util::ShortLivedLockFile lock_file("a/b/c/test");
-  util::LockFileGuard lock(lock_file);
-  CHECK(lock.acquired());
+  util::LongLivedLockFileManager lock_manager;
+  util::LockFile lock("a/b/c/test");
+  lock.make_long_lived(lock_manager);
+  CHECK(lock.acquire());
   CHECK(Stat::lstat("a/b/c/test.lock"));
 }
 
@@ -140,9 +119,10 @@ TEST_CASE("Break stale lock, blocking")
   util::set_timestamps("test.alive", long_time_ago);
   CHECK(symlink("foo", "test.lock") == 0);
 
-  util::LongLivedLockFile lock_file("test");
-  util::LockFileGuard lock(lock_file);
-  CHECK(lock.acquired());
+  util::LongLivedLockFileManager lock_manager;
+  util::LockFile lock("test");
+  lock.make_long_lived(lock_manager);
+  CHECK(lock.acquire());
 }
 
 TEST_CASE("Break stale lock, non-blocking")
@@ -154,8 +134,10 @@ TEST_CASE("Break stale lock, non-blocking")
   util::set_timestamps("test.alive", long_time_ago);
   CHECK(symlink("foo", "test.lock") == 0);
 
-  util::LongLivedLockFile lock_file("test");
-  util::LockFileGuard lock(lock_file, util::LockFileGuard::Mode::non_blocking);
+  util::LongLivedLockFileManager lock_manager;
+  util::LockFile lock("test");
+  lock.make_long_lived(lock_manager);
+  CHECK(lock.try_acquire());
   CHECK(lock.acquired());
 }
 #endif // !_WIN32
index 3624073..1a996c0 100644 (file)
@@ -33,6 +33,12 @@ TEST_CASE("TextTable")
     CHECK(table.render() == "");
   }
 
+  SUBCASE("only a heading")
+  {
+    table.add_heading("heading");
+    CHECK(table.render() == "heading\n");
+  }
+
   SUBCASE("1x1")
   {
     table.add_row({"a"});
index b1222b4..77e5c0b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 using TestUtil::TestContext;
 
+TEST_CASE("util::likely_size_on_disk")
+{
+  CHECK(util::likely_size_on_disk(0) == 0);
+  CHECK(util::likely_size_on_disk(1) == 4096);
+  CHECK(util::likely_size_on_disk(4095) == 4096);
+  CHECK(util::likely_size_on_disk(4096) == 4096);
+  CHECK(util::likely_size_on_disk(4097) == 8192);
+}
+
 TEST_CASE("util::read_file and util::write_file, text data")
 {
   TestContext test_context;
@@ -136,17 +145,32 @@ TEST_CASE("util::read_file_part")
 {
   CHECK(util::write_file("test", "banana"));
 
-  CHECK(util::read_file_part<util::Bytes>("test", 0, 0) == util::to_span(""));
-  CHECK(util::read_file_part<util::Bytes>("test", 0, 6)
-        == util::to_span("banana"));
-  CHECK(util::read_file_part<util::Bytes>("test", 0, 1000)
-        == util::to_span("banana"));
-
-  CHECK(util::read_file_part<util::Bytes>("test", 3, 0) == util::to_span(""));
-  CHECK(util::read_file_part<util::Bytes>("test", 3, 2) == util::to_span("an"));
-  CHECK(util::read_file_part<util::Bytes>("test", 3, 1000)
-        == util::to_span("ana"));
+  SUBCASE("util::Bytes")
+  {
+    CHECK(util::read_file_part<util::Bytes>("test", 0, 0) == util::to_span(""));
+    CHECK(util::read_file_part<util::Bytes>("test", 0, 6)
+          == util::to_span("banana"));
+    CHECK(util::read_file_part<util::Bytes>("test", 0, 1000)
+          == util::to_span("banana"));
+
+    CHECK(util::read_file_part<util::Bytes>("test", 3, 0) == util::to_span(""));
+    CHECK(util::read_file_part<util::Bytes>("test", 3, 2)
+          == util::to_span("an"));
+    CHECK(util::read_file_part<util::Bytes>("test", 3, 1000)
+          == util::to_span("ana"));
+
+    CHECK(util::read_file_part<util::Bytes>("test", 1000, 1000)
+          == util::to_span(""));
+  }
+  SUBCASE("std::vector<uint8_t>")
+  {
+    auto data = util::read_file_part<std::vector<uint8_t>>("test", 3, 2);
+    CHECK(*data == std::vector<uint8_t>{'a', 'n'});
+  }
 
-  CHECK(util::read_file_part<util::Bytes>("test", 1000, 1000)
-        == util::to_span(""));
+  SUBCASE("std::string")
+  {
+    auto data = util::read_file_part<std::string>("test", 3, 2);
+    CHECK(*data == "an");
+  }
 }
index 43defca..8bb703d 100644 (file)
@@ -131,6 +131,8 @@ TEST_CASE("util::path_starts_with")
   CHECK(util::path_starts_with("C:/foo/bar", "C:\\\\foo"));
   CHECK(util::path_starts_with("C:\\foo\\bar", "C:/foo"));
   CHECK(util::path_starts_with("C:\\\\foo\\\\bar", "C:/foo"));
+  CHECK(util::path_starts_with("C:/FOO/BAR", "c:\\foo"));
+  CHECK(util::path_starts_with("c:/foo/bar", "C:\\FOO"));
   CHECK(!util::path_starts_with("C:\\foo\\bar", "/foo/baz"));
   CHECK(!util::path_starts_with("C:\\foo\\bar", "C:/foo/baz"));
   CHECK(!util::path_starts_with("C:\\beh\\foo", "/foo"));
index 6269663..d6afeab 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 #include <vector>
 
 static bool
+operator==(std::pair<std::string, std::optional<std::string>> left,
+           std::pair<std::string, std::optional<std::string>> right)
+{
+  return left.first == right.first && left.second == right.second;
+}
+
+static bool
 operator==(std::pair<std::string_view, std::optional<std::string_view>> left,
            std::pair<std::string_view, std::optional<std::string_view>> right)
 {
@@ -50,6 +57,140 @@ TEST_CASE("util::ends_with")
   CHECK_FALSE(util::ends_with("x", "xy"));
 }
 
+TEST_CASE("util::format_human_readable_diff")
+{
+  using SUPT = util::SizeUnitPrefixType;
+
+  SUBCASE("binary")
+  {
+    CHECK(util::format_human_readable_diff(0, SUPT::binary) == "0 bytes");
+    CHECK(util::format_human_readable_diff(1, SUPT::binary) == "+1 byte");
+    CHECK(util::format_human_readable_diff(42, SUPT::binary) == "+42 bytes");
+    CHECK(util::format_human_readable_diff(1949, SUPT::binary) == "+1.9 KiB");
+    CHECK(util::format_human_readable_diff(1951, SUPT::binary) == "+1.9 KiB");
+    CHECK(util::format_human_readable_diff(499.7 * 1000, SUPT::binary)
+          == "+488.0 KiB");
+    CHECK(util::format_human_readable_diff(1000 * 1000, SUPT::binary)
+          == "+976.6 KiB");
+    CHECK(util::format_human_readable_diff(1234 * 1000, SUPT::binary)
+          == "+1.2 MiB");
+    CHECK(util::format_human_readable_diff(438.5 * 1000 * 1000, SUPT::binary)
+          == "+418.2 MiB");
+    CHECK(util::format_human_readable_diff(1000 * 1000 * 1000, SUPT::binary)
+          == "+953.7 MiB");
+    CHECK(
+      util::format_human_readable_diff(17.11 * 1000 * 1000 * 1000, SUPT::binary)
+      == "+15.9 GiB");
+
+    CHECK(util::format_human_readable_diff(-1, SUPT::binary) == "-1 byte");
+    CHECK(util::format_human_readable_diff(-42, SUPT::binary) == "-42 bytes");
+    CHECK(util::format_human_readable_diff(-1949, SUPT::binary) == "-1.9 KiB");
+    CHECK(util::format_human_readable_diff(-1951, SUPT::binary) == "-1.9 KiB");
+    CHECK(util::format_human_readable_diff(-499.7 * 1000, SUPT::binary)
+          == "-488.0 KiB");
+    CHECK(util::format_human_readable_diff(-1000 * 1000, SUPT::binary)
+          == "-976.6 KiB");
+    CHECK(util::format_human_readable_diff(-1234 * 1000, SUPT::binary)
+          == "-1.2 MiB");
+    CHECK(util::format_human_readable_diff(-438.5 * 1000 * 1000, SUPT::binary)
+          == "-418.2 MiB");
+    CHECK(util::format_human_readable_diff(-1000 * 1000 * 1000, SUPT::binary)
+          == "-953.7 MiB");
+    CHECK(util::format_human_readable_diff(-17.11 * 1000 * 1000 * 1000,
+                                           SUPT::binary)
+          == "-15.9 GiB");
+  }
+
+  SUBCASE("decimal")
+  {
+    CHECK(util::format_human_readable_diff(0, SUPT::decimal) == "0 bytes");
+    CHECK(util::format_human_readable_diff(1, SUPT::decimal) == "+1 byte");
+    CHECK(util::format_human_readable_diff(42, SUPT::decimal) == "+42 bytes");
+    CHECK(util::format_human_readable_diff(1949, SUPT::decimal) == "+1.9 kB");
+    CHECK(util::format_human_readable_diff(1951, SUPT::decimal) == "+2.0 kB");
+    CHECK(util::format_human_readable_diff(499.7 * 1000, SUPT::decimal)
+          == "+499.7 kB");
+    CHECK(util::format_human_readable_diff(1000 * 1000, SUPT::decimal)
+          == "+1.0 MB");
+    CHECK(util::format_human_readable_diff(1234 * 1000, SUPT::decimal)
+          == "+1.2 MB");
+    CHECK(util::format_human_readable_diff(438.5 * 1000 * 1000, SUPT::decimal)
+          == "+438.5 MB");
+    CHECK(util::format_human_readable_diff(1000 * 1000 * 1000, SUPT::decimal)
+          == "+1.0 GB");
+    CHECK(util::format_human_readable_diff(17.11 * 1000 * 1000 * 1000,
+                                           SUPT::decimal)
+          == "+17.1 GB");
+
+    CHECK(util::format_human_readable_diff(-1, SUPT::decimal) == "-1 byte");
+    CHECK(util::format_human_readable_diff(-42, SUPT::decimal) == "-42 bytes");
+    CHECK(util::format_human_readable_diff(-1949, SUPT::decimal) == "-1.9 kB");
+    CHECK(util::format_human_readable_diff(-1951, SUPT::decimal) == "-2.0 kB");
+    CHECK(util::format_human_readable_diff(-499.7 * 1000, SUPT::decimal)
+          == "-499.7 kB");
+    CHECK(util::format_human_readable_diff(-1000 * 1000, SUPT::decimal)
+          == "-1.0 MB");
+    CHECK(util::format_human_readable_diff(-1234 * 1000, SUPT::decimal)
+          == "-1.2 MB");
+    CHECK(util::format_human_readable_diff(-438.5 * 1000 * 1000, SUPT::decimal)
+          == "-438.5 MB");
+    CHECK(util::format_human_readable_diff(-1000 * 1000 * 1000, SUPT::decimal)
+          == "-1.0 GB");
+    CHECK(util::format_human_readable_diff(-17.11 * 1000 * 1000 * 1000,
+                                           SUPT::decimal)
+          == "-17.1 GB");
+  }
+}
+
+TEST_CASE("util::format_human_readable_size")
+{
+  using SUPT = util::SizeUnitPrefixType;
+
+  SUBCASE("binary")
+  {
+    CHECK(util::format_human_readable_size(0, SUPT::binary) == "0 bytes");
+    CHECK(util::format_human_readable_size(1, SUPT::binary) == "1 byte");
+    CHECK(util::format_human_readable_size(42, SUPT::binary) == "42 bytes");
+    CHECK(util::format_human_readable_size(1949, SUPT::binary) == "1.9 KiB");
+    CHECK(util::format_human_readable_size(1951, SUPT::binary) == "1.9 KiB");
+    CHECK(util::format_human_readable_size(499.7 * 1000, SUPT::binary)
+          == "488.0 KiB");
+    CHECK(util::format_human_readable_size(1000 * 1000, SUPT::binary)
+          == "976.6 KiB");
+    CHECK(util::format_human_readable_size(1234 * 1000, SUPT::binary)
+          == "1.2 MiB");
+    CHECK(util::format_human_readable_size(438.5 * 1000 * 1000, SUPT::binary)
+          == "418.2 MiB");
+    CHECK(util::format_human_readable_size(1000 * 1000 * 1000, SUPT::binary)
+          == "953.7 MiB");
+    CHECK(
+      util::format_human_readable_size(17.11 * 1000 * 1000 * 1000, SUPT::binary)
+      == "15.9 GiB");
+  }
+
+  SUBCASE("decimal")
+  {
+    CHECK(util::format_human_readable_size(0, SUPT::decimal) == "0 bytes");
+    CHECK(util::format_human_readable_size(1, SUPT::decimal) == "1 byte");
+    CHECK(util::format_human_readable_size(42, SUPT::decimal) == "42 bytes");
+    CHECK(util::format_human_readable_size(1949, SUPT::decimal) == "1.9 kB");
+    CHECK(util::format_human_readable_size(1951, SUPT::decimal) == "2.0 kB");
+    CHECK(util::format_human_readable_size(499.7 * 1000, SUPT::decimal)
+          == "499.7 kB");
+    CHECK(util::format_human_readable_size(1000 * 1000, SUPT::decimal)
+          == "1.0 MB");
+    CHECK(util::format_human_readable_size(1234 * 1000, SUPT::decimal)
+          == "1.2 MB");
+    CHECK(util::format_human_readable_size(438.5 * 1000 * 1000, SUPT::decimal)
+          == "438.5 MB");
+    CHECK(util::format_human_readable_size(1000 * 1000 * 1000, SUPT::decimal)
+          == "1.0 GB");
+    CHECK(util::format_human_readable_size(17.11 * 1000 * 1000 * 1000,
+                                           SUPT::decimal)
+          == "17.1 GB");
+  }
+}
+
 TEST_CASE("util::join")
 {
   {
@@ -126,6 +267,47 @@ TEST_CASE("util::parse_signed")
         == "banana must be between 1 and 2");
 }
 
+TEST_CASE("util::parse_size")
+{
+  using SUPT = util::SizeUnitPrefixType;
+
+  auto u64 = [](auto i) { return static_cast<uint64_t>(i); };
+  auto h = [&](auto size, auto st) { return std::make_pair(u64(size), st); };
+
+  // Default suffix: Gi
+  CHECK(*util::parse_size("0") == h(0, SUPT::binary));
+  CHECK(*util::parse_size("42")
+        == h(u64(42) * 1024 * 1024 * 1024, SUPT::binary));
+
+  // Decimal suffixes
+  CHECK(*util::parse_size("78k") == h(78 * 1000, SUPT::decimal));
+  CHECK(*util::parse_size("78K") == h(78 * 1000, SUPT::decimal));
+  CHECK(*util::parse_size("1.1 M") == h(u64(1.1 * 1000 * 1000), SUPT::decimal));
+  CHECK(*util::parse_size("438.55M")
+        == h(u64(438.55 * 1000 * 1000), SUPT::decimal));
+  CHECK(*util::parse_size("1 G") == h(1 * 1000 * 1000 * 1000, SUPT::decimal));
+  CHECK(*util::parse_size("2T")
+        == h(u64(2) * 1000 * 1000 * 1000 * 1000, SUPT::decimal));
+
+  // Binary suffixes
+  CHECK(*util::parse_size("78 Ki") == h(78 * 1024, SUPT::binary));
+  CHECK(*util::parse_size("1.1Mi") == h(u64(1.1 * 1024 * 1024), SUPT::binary));
+  CHECK(*util::parse_size("438.55 Mi")
+        == h(u64(438.55 * 1024 * 1024), SUPT::binary));
+  CHECK(*util::parse_size("1Gi") == h(1 * 1024 * 1024 * 1024, SUPT::binary));
+  CHECK(*util::parse_size("2 Ti")
+        == h(u64(2) * 1024 * 1024 * 1024 * 1024, SUPT::binary));
+
+  // With B suffix
+  CHECK(*util::parse_size("9MB") == h(9 * 1000 * 1000, SUPT::decimal));
+  CHECK(*util::parse_size("9MiB") == h(9 * 1024 * 1024, SUPT::binary));
+
+  // Errors
+  CHECK(util::parse_size("").error() == "invalid size: \"\"");
+  CHECK(util::parse_size("x").error() == "invalid size: \"x\"");
+  CHECK(util::parse_size("10x").error() == "invalid size: \"10x\"");
+}
+
 TEST_CASE("util::parse_umask")
 {
   CHECK(util::parse_umask("1") == 1U);
@@ -230,15 +412,44 @@ TEST_CASE("util::split_once")
   using std::nullopt;
   using util::split_once;
 
-  CHECK(split_once("", '=') == make_pair("", nullopt));
-  CHECK(split_once("a", '=') == make_pair("a", nullopt));
-  CHECK(split_once("=a", '=') == make_pair("", "a"));
-  CHECK(split_once("a=", '=') == make_pair("a", ""));
-  CHECK(split_once("a==", '=') == make_pair("a", "="));
-  CHECK(split_once("a=b", '=') == make_pair("a", "b"));
-  CHECK(split_once("a=b=", '=') == make_pair("a", "b="));
-  CHECK(split_once("a=b=c", '=') == make_pair("a", "b=c"));
-  CHECK(split_once("x y", ' ') == make_pair("x", "y"));
+  SUBCASE("const char*")
+  {
+    CHECK(split_once("", '=') == make_pair("", nullopt));
+    CHECK(split_once("a", '=') == make_pair("a", nullopt));
+    CHECK(split_once("=a", '=') == make_pair("", "a"));
+    CHECK(split_once("a=", '=') == make_pair("a", ""));
+    CHECK(split_once("a==", '=') == make_pair("a", "="));
+    CHECK(split_once("a=b", '=') == make_pair("a", "b"));
+    CHECK(split_once("a=b=", '=') == make_pair("a", "b="));
+    CHECK(split_once("a=b=c", '=') == make_pair("a", "b=c"));
+    CHECK(split_once("x y", ' ') == make_pair("x", "y"));
+  }
+
+  SUBCASE("std::string&&")
+  {
+    CHECK(split_once(std::string(""), '=') == make_pair("", nullopt));
+    CHECK(split_once(std::string("a"), '=') == make_pair("a", nullopt));
+    CHECK(split_once(std::string("=a"), '=') == make_pair("", "a"));
+    CHECK(split_once(std::string("a="), '=') == make_pair("a", ""));
+    CHECK(split_once(std::string("a=="), '=') == make_pair("a", "="));
+    CHECK(split_once(std::string("a=b"), '=') == make_pair("a", "b"));
+    CHECK(split_once(std::string("a=b="), '=') == make_pair("a", "b="));
+    CHECK(split_once(std::string("a=b=c"), '=') == make_pair("a", "b=c"));
+    CHECK(split_once(std::string("x y"), ' ') == make_pair("x", "y"));
+  }
+
+  SUBCASE("std::string_view")
+  {
+    CHECK(split_once(std::string_view(""), '=') == make_pair("", nullopt));
+    CHECK(split_once(std::string_view("a"), '=') == make_pair("a", nullopt));
+    CHECK(split_once(std::string_view("=a"), '=') == make_pair("", "a"));
+    CHECK(split_once(std::string_view("a="), '=') == make_pair("a", ""));
+    CHECK(split_once(std::string_view("a=="), '=') == make_pair("a", "="));
+    CHECK(split_once(std::string_view("a=b"), '=') == make_pair("a", "b"));
+    CHECK(split_once(std::string_view("a=b="), '=') == make_pair("a", "b="));
+    CHECK(split_once(std::string_view("a=b=c"), '=') == make_pair("a", "b=c"));
+    CHECK(split_once(std::string_view("x y"), ' ') == make_pair("x", "y"));
+  }
 }
 
 TEST_CASE("util::starts_with")