Repository: ashvardanian/StringZilla (language: rust)

Issue:
Faster sorting algorithm
The original sorting algorithm in StringZilla had two passes - Radix and QuickSort. The first one sorted strings by the first 32 bits, and the second operated on chunks with equivalent prefixes. For QuickSort, we use the `qsort` function, which has different signatures on different Operating Systems.

```c
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
        qsort_s(sequence->order, split, sizeof(sz_size_t), qsort_comparator, (void *)sequence);
        qsort_s(sequence->order + split,
                sequence->count - split,
                sizeof(sz_size_t),
                qsort_comparator,
                (void *)sequence);
#elif __APPLE__
        qsort_r(sequence->order, split, sizeof(sz_size_t), (void *)sequence, qsort_comparator);
        qsort_r(sequence->order + split,
                sequence->count - split,
                sizeof(sz_size_t),
                (void *)sequence,
                qsort_comparator);
#else
        qsort_r(sequence->order, split, sizeof(sz_size_t), qsort_comparator, (void *)sequence);
        qsort_r(sequence->order + split,
                sequence->count - split,
                sizeof(sz_size_t),
                qsort_comparator,
                (void *)sequence);
#endif
```

It's ugly and slow, especially compared to the `std::sort` in the C++ Standard Templates Library. Let's replace that with an excellent specialized algorithm to achieve higher performance! After some initial testing, I know that doubling the speed is possible, but there may be even more room for improvement!



Discussion / hints:

Fix patch:
diff --git a/.clang-format b/.clang-format
index 83b48aa7..cda5673b 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,8 +1,8 @@
 Language: Cpp
-BasedOnStyle:  LLVM
+BasedOnStyle: LLVM
 IndentWidth: 4
 TabWidth: 4
-NamespaceIndentation: All
+NamespaceIndentation: None
 ColumnLimit: 120
 ReflowComments: true
 UseTab: Never
@@ -44,9 +44,8 @@ BraceWrapping:
   SplitEmptyNamespace: false
   IndentBraces: false
 
-
 SortIncludes: true
-SortUsingDeclarations: true 
+SortUsingDeclarations: true
 
 SpaceAfterCStyleCast: false
 SpaceAfterLogicalNot: false
@@ -65,5 +64,7 @@ SpacesInContainerLiterals: false
 SpacesInParentheses: false
 SpacesInSquareBrackets: false
 
-BinPackArguments: false
-BinPackParameters: false
+BinPackArguments: true
+BinPackParameters: true
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakArgument: 1
diff --git a/.github/workflows/build_tools.sh b/.github/workflows/build_tools.sh
new file mode 100755
index 00000000..e1cde117
--- /dev/null
+++ b/.github/workflows/build_tools.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# Assign arguments to variables
+BUILD_TYPE=$1    # Debug or Release
+COMPILER=$2      # GCC, LLVM, or MSVC
+
+# Set common flags
+COMMON_FLAGS="-DSTRINGZILLA_BUILD_TEST=1 -DSTRINGZILLA_BUILD_BENCHMARK=1 -DSTRINGZILLA_BUILD_SHARED=0"
+
+# Compiler specific settings
+case "$COMPILER" in
+    "GCC")
+        COMPILER_FLAGS="-DCMAKE_CXX_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12"
+        ;;
+    "LLVM")
+        COMPILER_FLAGS="-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
+        ;;
+    "MSVC")
+        COMPILER_FLAGS="" 
+        ;;
+    *)
+        echo "Unknown compiler: $COMPILER"
+        exit 1
+        ;;
+esac
+
+# Set build type
+case "$BUILD_TYPE" in
+    "Debug")
+        BUILD_DIR="./build_debug"
+        BUILD_FLAGS="-DCMAKE_BUILD_TYPE=Debug"
+        ;;
+    "Release")
+        BUILD_DIR="./build_release"
+        BUILD_FLAGS="-DCMAKE_BUILD_TYPE=RelWithDebInfo"
+        ;;
+    *)
+        echo "Unknown build type: $BUILD_TYPE"
+        exit 1
+        ;;
+esac
+
+# Execute commands
+cmake $COMMON_FLAGS $COMPILER_FLAGS $BUILD_FLAGS -B $BUILD_DIR && cmake --build $BUILD_DIR --config $BUILD_TYPE
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index dd09096b..7530d8ab 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -9,6 +9,8 @@ on:
 env:
   BUILD_TYPE: Release
   GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
+  PYTHON_VERSION: 3.11
+  SWIFT_VERSION: 5.9
   PYTHONUTF8: 1
 
 # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
@@ -16,76 +18,247 @@ permissions:
   contents: read
 
 jobs:
-
-  test_python_311:
-    name: Test Python
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os: [ubuntu-20.04, macOS-11, windows-2022]
-        python-version: ["3.11"]
+  test_ubuntu_gcc:
+    name: Ubuntu (GCC 12)
+    runs-on: ubuntu-22.04
+    env:
+      CC: gcc-12
+      CXX: g++-12
 
     steps:
-      - uses: actions/checkout@v3
-      - run: git submodule update --init --recursive
-      - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v4
+      - uses: actions/checkout@v4
+
+        # C/C++
+        # If the compilation fails, we want to log the compilation commands in addition to
+        # the standard output.
+      - name: Build C/C++
+        run: |
+          sudo apt update
+          sudo apt install -y cmake build-essential libjemalloc-dev libomp-dev gcc-12 g++-12
+
+          cmake -B build_artifacts \
+            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+            -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+            -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+            -DSTRINGZILLA_BUILD_TEST=1
+
+          cmake --build build_artifacts --config RelWithDebInfo > build_artifacts/logs.txt 2>&1 || {
+            echo "Compilation failed. Here are the logs:"
+            cat build_artifacts/logs.txt
+            echo "The original compilation commands:"
+            cat build_artifacts/compile_commands.json
+            echo "CPU Features:"
+            lscpu
+            echo "GCC Version:"
+            gcc-12 --version
+            echo "G++ Version:"
+            g++-12 --version
+            exit 1
+          }
+      - name: Test C++
+        run: ./build_artifacts/stringzilla_test_cpp20
+      - name: Test on Real World Data
+        run: |
+          ./build_artifacts/stringzilla_bench_search ${DATASET_PATH}     # for substring search
+          ./build_artifacts/stringzilla_bench_token ${DATASET_PATH}      # for hashing, equality comparisons, etc.
+          ./build_artifacts/stringzilla_bench_similarity ${DATASET_PATH} # for edit distances and alignment scores
+          ./build_artifacts/stringzilla_bench_sort ${DATASET_PATH}       # for sorting arrays of strings
+          ./build_artifacts/stringzilla_bench_container ${DATASET_PATH}  # for STL containers with string keys
+        env:
+          DATASET_PATH: ./README.md
+        # Don't overload GitHub with our benchmarks.
+        # The results in such an unstable environment will be meaningless anyway.
+        if: 0
+
+        # Python
+      - name: Set up Python ${{ env.PYTHON_VERSION }}
+        uses: actions/setup-python@v5
         with:
-          python-version: ${{ matrix.python-version }}
-      - name: Install dependencies
+          python-version: ${{ env.PYTHON_VERSION }}
+      - name: Build Python
         run: |
-          python -m pip install --no-cache-dir --upgrade pip numpy
-          pip install --no-cache-dir pytest
-      - name: Build locally
-        run: python -m pip install .
-      - name: Test with PyTest
-        run: pytest scripts/
-
-
-  test_python_37:
-    name: Test Python 3.7
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os: [ubuntu-20.04]
-        python-version: ["3.7"]
+          python -m pip install --upgrade pip
+          pip install pytest pytest-repeat numpy
+          python -m pip install .
+      - name: Test Python
+        run: pytest scripts/test.py -s -x
+
+        # JavaScript
+      # - name: Set up Node.js
+      #   uses: actions/setup-node
+      #   with:
+      #     node-version: 18
+      # - name: Build and test JavaScript
+      #   run: npm ci && npm test
+
+        # Rust
+      - name: Test Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
+
+  test_ubuntu_clang:
+    name: Ubuntu (Clang 16)
+    runs-on: ubuntu-22.04
+    env:
+      CC: clang-16
+      CXX: clang++-16
 
     steps:
-      - uses: actions/checkout@v3
-      - run: git submodule update --init --recursive
-      
-      - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v4
+      - uses: actions/checkout@v4
         with:
-          python-version: ${{ matrix.python-version }}
+          ref: main-dev
+      - run: git submodule update --init --recursive
 
-      - name: Install dependencies
+        # C/C++
+        # Clang 16 isn't available from default repos on Ubuntu 22.04, so we have to install it manually
+      - name: Build C/C++
         run: |
-          python -m pip install --no-cache-dir --upgrade pip numpy
-          pip install --no-cache-dir pytest
+          sudo apt update
+          sudo apt install -y cmake build-essential libjemalloc-dev
+          wget https://apt.llvm.org/llvm.sh
+          chmod +x llvm.sh
+          sudo ./llvm.sh 16
 
-      - name: Build locally
-        run: python -m pip install .
+          cmake -B build_artifacts \
+            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+            -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+            -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+            -DSTRINGZILLA_BUILD_TEST=1
 
-      - name: Test with PyTest
-        run: pytest scripts/
+          cmake --build build_artifacts --config RelWithDebInfo > build_artifacts/logs.txt 2>&1 || {
+            echo "Compilation failed. Here are the logs:"
+            cat build_artifacts/logs.txt
+            echo "The original compilation commands:"
+            cat build_artifacts/compile_commands.json
+            echo "CPU Features:"
+            lscpu
+            echo "Clang Version:"
+            clang-16 --version
+            echo "Clang++ Version:"
+            clang++-16 --version
+            exit 1
+          }
+      - name: Test C++
+        run: ./build_artifacts/stringzilla_test_cpp20
+      - name: Test on Real World Data
+        run: |
+          ./build_artifacts/stringzilla_bench_search ${DATASET_PATH}     # for substring search
+          ./build_artifacts/stringzilla_bench_token ${DATASET_PATH}      # for hashing, equality comparisons, etc.
+          ./build_artifacts/stringzilla_bench_similarity ${DATASET_PATH} # for edit distances and alignment scores
+          ./build_artifacts/stringzilla_bench_sort ${DATASET_PATH}       # for sorting arrays of strings
+          ./build_artifacts/stringzilla_bench_container ${DATASET_PATH}  # for STL containers with string keys
+        env:
+          DATASET_PATH: ./README.md
+        # Don't overload GitHub with our benchmarks.
+        # The results in such an unstable environment will be meaningless anyway.
+        if: 0
 
-  test_javascript:
-    name: Test JavaScript
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        node-version: [18.x]
+        # Python
+      - name: Set up Python ${{ env.PYTHON_VERSION }}
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+      - name: Build Python
+        run: |
+          python -m pip install --upgrade pip
+          pip install pytest pytest-repeat numpy
+          python -m pip install .
+      - name: Test Python
+        run: pytest scripts/test.py -s -x
+
+        # Rust
+      - name: Test Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
+
+        # Swift
+        # Fails due to: https://github.com/swift-actions/setup-swift/issues/591
+      # - name: Set up Swift ${{ env.SWIFT_VERSION }}
+      #   uses: swift-actions/setup-swift@v1
+      #   with:
+      #     swift-version: ${{ env.SWIFT_VERSION }}
+      # - name: Build Swift
+      #   run: swift build -c release --static-swift-stdlib
+      # - name: Test Swift
+      #   run: swift test -c release --enable-test-discovery
+
+  # Temporary workaround to run Swift tests on Linux
+  # Based on: https://github.com/swift-actions/setup-swift/issues/591#issuecomment-1685710678
+  test_ubuntu_swift:
+    name: Ubuntu (Swift)
+    runs-on: ubuntu-22.04
+    container: swift:5.9
     steps:
-    
       - uses: actions/checkout@v4
-      - name: Set up Node.js
-        uses: actions/setup-node@v3
+      - name: Test Swift
+        run: swift test
+
+  test_macos:
+    name: MacOS
+    runs-on: macos-12
+
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          ref: main-dev
+      - run: git submodule update --init --recursive
+
+        # C/C++
+      - name: Build C/C++
+        run: |
+          brew update
+          brew install cmake
+          cmake -B build_artifacts \
+            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+            -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+            -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+            -DSTRINGZILLA_BUILD_TEST=1
+          cmake --build build_artifacts --config RelWithDebInfo
+      - name: Test C++
+        run: ./build_artifacts/stringzilla_test_cpp17
+      - name: Test on Real World Data
+        run: |
+          ./build_artifacts/stringzilla_bench_search ${DATASET_PATH}     # for substring search
+          ./build_artifacts/stringzilla_bench_token ${DATASET_PATH}      # for hashing, equality comparisons, etc.
+          ./build_artifacts/stringzilla_bench_similarity ${DATASET_PATH} # for edit distances and alignment scores
+          ./build_artifacts/stringzilla_bench_sort ${DATASET_PATH}       # for sorting arrays of strings
+          ./build_artifacts/stringzilla_bench_container ${DATASET_PATH}  # for STL containers with string keys
+        env:
+          DATASET_PATH: ./README.md
+        # Don't overload GitHub with our benchmarks.
+        # The results in such an unstable environment will be meaningless anyway.
+        if: 0
+
+        # Python
+      - name: Set up Python ${{ env.PYTHON_VERSION }}
+        uses: actions/setup-python@v5
         with:
-          node-version: '18.x'
-      
-      - name: Build locally
-        run: npm i
+          python-version: ${{ env.PYTHON_VERSION }}
+      - name: Build Python
+        run: |
+          python -m pip install --upgrade pip
+          pip install pytest pytest-repeat numpy
+          python -m pip install .
+      - name: Test Python
+        run: pytest scripts/test.py -s -x
+
+        # Swift
+      - name: Set up Swift ${{ env.SWIFT_VERSION }}
+        uses: swift-actions/setup-swift@v1
+        with:
+          swift-version: ${{ env.SWIFT_VERSION }}
+      - name: Build Swift
+        run: swift build
+      - name: Test Swift
+        run: swift test
 
-      - name: Test
-        run: npm test
+        # Rust
+      - name: Test Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b96529a7..a54a4865 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -110,21 +110,39 @@ jobs:
           verbose: true
           print-hash: true
 
-  publish_javascript:
-    name: Publish JavaScript
+  publish_rust:
+    name: Publish Rust
     needs: versioning
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v3
         with:
-          ref: 'main'  
+          ref: "main"
       - run: git submodule update --init --recursive
-      - uses: actions/setup-node@v3
+      - uses: actions-rs/toolchain@v1
         with:
-          node-version: 18
-      - run: npm install
-      - run: npm ci
-      - run: npm test
-      - uses: JS-DevTools/npm-publish@v2
+          toolchain: stable
+          override: true
+      - uses: katyo/publish-crates@v2
         with:
-          token: ${{ secrets.NPM_TOKEN }}
+          registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+
+  # Let's not publish the JavaScript package for now
+  # publish_javascript:
+  #   name: Publish JavaScript
+  #   needs: versioning
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v3
+  #       with:
+  #         ref: 'main'  
+  #     - run: git submodule update --init --recursive
+  #     - uses: actions/setup-node@v3
+  #       with:
+  #         node-version: 18
+  #     - run: npm install
+  #     - run: npm ci
+  #     - run: npm test
+  #     - uses: JS-DevTools/npm-publish@v2
+  #       with:
+  #         token: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/update_version.sh b/.github/workflows/update_version.sh
index 5f4dfb73..a2fcaeec 100644
--- a/.github/workflows/update_version.sh
+++ b/.github/workflows/update_version.sh
@@ -1,4 +1,9 @@
 #!/bin/sh
 
 echo $1 > VERSION && 
-    sed -i "s/\"version\": \".*\"/\"version\": \"$1\"/" package.json
+    sed -i "s/^\(#define STRINGZILLA_VERSION_MAJOR \).*/\1$(echo "$1" | cut -d. -f1)/" ./include/stringzilla/stringzilla.h &&
+    sed -i "s/^\(#define STRINGZILLA_VERSION_MINOR \).*/\1$(echo "$1" | cut -d. -f2)/" ./include/stringzilla/stringzilla.h &&
+    sed -i "s/^\(#define STRINGZILLA_VERSION_PATCH \).*/\1$(echo "$1" | cut -d. -f3)/" ./include/stringzilla/stringzilla.h &&
+    sed -i "s/^version = \".*\"/version = \"$1\"/" Cargo.toml &&
+    sed -i "s/\"version\": \".*\"/\"version\": \"$1\"/" package.json &&
+    sed -i "s/VERSION [0-9]\+\.[0-9]\+\.[0-9]\+/VERSION $1/" CMakeLists.txt
diff --git a/.gitignore b/.gitignore
index 82845490..412c78b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,19 @@
+# Primary build folders
 build/
-.DS_Store
-tmp/
 build_debug/
 build_release/
+
+# Yes, everyone loves keeping this file in the history.
+# But with a very minimalistic binding and just a couple of dependencies 
+# it brings 7000 lines of text polluting the entire repo.
+package-lock.json
+
+# Temporary files
+.DS_Store
+.swiftpm/
+.build/
+tmp/
+target/
 __pycache__
 .pytest_cache
 .conda
@@ -10,12 +21,14 @@ Makefile
 CMakeCache.txt
 cmake_install.cmake
 CMakeFiles
-CMakeLists.txt~
-substr_search_cpp
 *.so
 *.egg-info
 *.whl
 *.pyd
+.venv/*
 node_modules/
 
-leipzig1M.txt
\ No newline at end of file
+# Recommended datasets
+leipzig1M.txt
+enwik9.txt
+xlsum.csv
diff --git a/.releaserc b/.releaserc
index ab603bc1..faf8401e 100644
--- a/.releaserc
+++ b/.releaserc
@@ -77,6 +77,8 @@
                     "conanfile.py",
                     "package.json",
                     "Cargo.toml",
+                    "CMakeLists.txt",
+                    "include/stringzilla/stringzilla.h"
                 ],
                 "message": "Build: Released ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
             }
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000..17ff408a
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,11 @@
+{
+    "recommendations": [
+        "ms-vscode.cpptools-themes",
+        "ms-vscode.cmake-tools",
+        "ms-python.python",
+        "ms-python.black-formatter",
+        "yzhang.markdown-all-in-one",
+        "aaron-bond.better-comments",
+        "cheshirekow.cmake-format",
+    ]
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d6e1e9a7..d899d3fe 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,30 +1,99 @@
 {
-    // Use IntelliSense to learn about possible attributes.
-    // Hover to view descriptions of existing attributes.
-    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
-    "version": "0.2.0",
-    "configurations": [
+  // Use IntelliSense to learn about possible attributes.
+  // Hover to view descriptions of existing attributes.
+  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Debug C++ Unit Tests",
+      "type": "cppdbg",
+      "request": "launch",
+      "program": "${workspaceFolder}/build_debug/stringzilla_test_cpp20",
+      "cwd": "${workspaceFolder}",
+      "environment": [
         {
-            "name": "Test",
-            "type": "cppdbg",
-            "request": "launch",
-            "program": "${workspaceFolder}/build_debug/stringzilla_test",
-            "cwd": "${workspaceFolder}",
-            "environment": [
-                {
-                    "name": "ASAN_OPTIONS",
-                    "value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1"
-                }
-            ],
-            "stopAtEntry": false,
-            "linux": {
-                "preLaunchTask": "Linux Build C++ Test Debug",
-                "MIMode": "gdb"
-            },
-            "osx": {
-                "preLaunchTask": "MacOS Build C++ Test Debug",
-                "MIMode": "lldb"
-            }
+          "name": "ASAN_OPTIONS",
+          "value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1"
         }
-    ]
+      ],
+      "stopAtEntry": false,
+      "linux": {
+        "preLaunchTask": "Build with GCC: Debug",
+        "MIMode": "gdb"
+      },
+      "osx": {
+        "preLaunchTask": "Build with LLVM: Debug",
+        "MIMode": "lldb"
+      },
+      "windows": {
+        "program": "${workspaceFolder}\\build_debug\\stringzilla_test_cpp20.exe",
+        "preLaunchTask": "Build with MSVC: Debug",
+        "MIMode": "gdb",
+        "miDebuggerPath": "C:\\MinGw\\bin\\gdb.exe"
+      }
+    },
+    {
+      "name": "Current C++ Benchmark",
+      "type": "cppdbg",
+      "request": "launch",
+      "program": "${workspaceFolder}/build_debug/stringzilla_${fileBasenameNoExtension}",
+      "args": [
+        "leipzig1M.txt"
+      ],
+      "cwd": "${workspaceFolder}",
+      "environment": [
+        {
+          "name": "ASAN_OPTIONS",
+          "value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1"
+        }
+      ],
+      "stopAtEntry": false,
+      "linux": {
+        "preLaunchTask": "Build with GCC: Debug",
+        "MIMode": "gdb"
+      },
+      "osx": {
+        "preLaunchTask": "Build with LLVM: Debug",
+        "MIMode": "lldb"
+      },
+      "windows": {
+        "program": "${workspaceFolder}\\build_debug\\stringzilla_${fileBasenameNoExtension}.exe",
+        "preLaunchTask": "Build with MSVC: Debug",
+        "MIMode": "gdb",
+        "miDebuggerPath": "C:\\MinGw\\bin\\gdb.exe"
+      }
+    },
+    {
+      "name": "Current Python File",
+      "type": "python",
+      "request": "launch",
+      "program": "${file}",
+      "console": "integratedTerminal",
+      "justMyCode": true
+    },
+    {
+      "name": "Current Python File with Leipzig1M arg",
+      "type": "python",
+      "request": "launch",
+      "program": "${file}",
+      "console": "integratedTerminal",
+      "justMyCode": true,
+      "args": [
+        "./leipzig1M.txt"
+      ],
+    },
+    {
+      "name": "Current PyTest File",
+      "type": "python",
+      "request": "launch",
+      "module": "pytest",
+      "args": [
+        "${file}",
+        "-s",
+        "-x"
+      ],
+      "console": "integratedTerminal",
+      "justMyCode": false
+    }
+  ]
 }
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 575441f2..035d8f84 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,177 +1,235 @@
 {
+  "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
   // This may cause overheating.
   // https://github.com/microsoft/vscode-cpptools/issues/1816
   "C_Cpp.workspaceParsingPriority": "low",
-  "cmake.configureOnOpen": true,
   "cmake.buildDirectory": "${workspaceRoot}/build",
-  "cmake.sourceDirectory": "${workspaceRoot}",
-  "editor.rulers": [
-    120
-  ],
+  "cmake.configureOnOpen": true,
   // https://github.com/microsoft/vscode-cpptools/issues/2456#issuecomment-439295153
   "cmake.debugConfig": {
-    "stopAtEntry": false,
-    "MIMode": "lldb",
     "logging": {
-      "trace": true,
       "engineLogging": true,
+      "trace": true,
       "traceResponse": true
-    }
-  },
-  "editor.formatOnSave": true,
-  "python.pythonPath": "/Users/av/miniconda3/bin/python",
-  "files.associations": {
-    "string_view": "cpp",
-    "array": "cpp",
-    "atomic": "cpp",
-    "bit": "cpp",
-    "*.tcc": "cpp",
-    "cctype": "cpp",
-    "clocale": "cpp",
-    "cmath": "cpp",
-    "complex": "cpp",
-    "cstdarg": "cpp",
-    "cstddef": "cpp",
-    "cstdint": "cpp",
-    "cstdio": "cpp",
-    "cstdlib": "cpp",
-    "cwchar": "cpp",
-    "cwctype": "cpp",
-    "deque": "cpp",
-    "set": "cpp",
-    "unordered_map": "cpp",
-    "unordered_set": "cpp",
-    "vector": "cpp",
-    "exception": "cpp",
-    "algorithm": "cpp",
-    "functional": "cpp",
-    "iterator": "cpp",
-    "memory": "cpp",
-    "memory_resource": "cpp",
-    "numeric": "cpp",
-    "optional": "cpp",
-    "random": "cpp",
-    "string": "cpp",
-    "system_error": "cpp",
-    "tuple": "cpp",
-    "type_traits": "cpp",
-    "utility": "cpp",
-    "fstream": "cpp",
-    "initializer_list": "cpp",
-    "iosfwd": "cpp",
-    "istream": "cpp",
-    "limits": "cpp",
-    "new": "cpp",
-    "ostream": "cpp",
-    "sstream": "cpp",
-    "stdexcept": "cpp",
-    "streambuf": "cpp",
-    "typeinfo": "cpp",
-    "map": "cpp",
-    "__bit_reference": "cpp",
-    "__config": "cpp",
-    "__debug": "cpp",
-    "__errc": "cpp",
-    "__functional_base": "cpp",
-    "__hash_table": "cpp",
-    "__locale": "cpp",
-    "__mutex_base": "cpp",
-    "__node_handle": "cpp",
-    "__nullptr": "cpp",
-    "__split_buffer": "cpp",
-    "__string": "cpp",
-    "__threading_support": "cpp",
-    "__tree": "cpp",
-    "__tuple": "cpp",
-    "bitset": "cpp",
-    "chrono": "cpp",
-    "codecvt": "cpp",
-    "condition_variable": "cpp",
-    "cstring": "cpp",
-    "ctime": "cpp",
-    "forward_list": "cpp",
-    "iomanip": "cpp",
-    "ios": "cpp",
-    "iostream": "cpp",
-    "locale": "cpp",
-    "mutex": "cpp",
-    "queue": "cpp",
-    "ratio": "cpp",
-    "stack": "cpp",
-    "thread": "cpp",
-    "typeindex": "cpp",
-    "cinttypes": "cpp",
-    "__bits": "cpp",
-    "any": "cpp",
-    "compare": "cpp",
-    "concepts": "cpp",
-    "csignal": "cpp",
-    "future": "cpp",
-    "list": "cpp",
-    "numbers": "cpp",
-    "semaphore": "cpp",
-    "span": "cpp",
-    "variant": "cpp",
-    "source_location": "cpp",
-    "stop_token": "cpp",
-    "__verbose_abort": "cpp",
-    "strstream": "cpp",
-    "filesystem": "cpp",
-    "stringzilla.h": "c",
-    "__memory": "c",
-    "charconv": "c"
+    },
+    "MIMode": "lldb",
+    "stopAtEntry": false
   },
-  "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
+  "cmake.sourceDirectory": "${workspaceRoot}",
   "cSpell.words": [
-    "abababab",
     "allowoverlap",
+    "aminoacid",
+    "aminoacids",
+    "Apostolico",
+    "Appleby",
+    "ASAN",
+    "ashvardanian",
+    "Baeza",
     "basicsize",
     "bigram",
+    "bioinformaticians",
+    "bioinformatics",
+    "Bitap",
+    "bitcast",
+    "BLOSUM",
+    "Brumme",
+    "Cawley",
+    "cheminformatics",
     "cibuildwheel",
+    "CONCAT",
+    "copydoc",
+    "cptr",
     "endregion",
     "endswith",
+    "Eron",
+    "Fisher",
+    "Galil",
     "getitem",
     "getslice",
+    "Giancarlo",
+    "Gonnet",
+    "Hirschberg's",
+    "Horspool",
     "initproc",
     "intp",
+    "isprintable",
     "itemsize",
+    "Jaccard",
+    "Karp",
     "keeplinebreaks",
     "keepseparator",
+    "Kernighan",
     "kwargs",
     "kwds",
     "kwnames",
-    "levenstein",
+    "Lemire",
+    "Levenshtein",
+    "libdivide",
+    "lstrip",
+    "Manber",
     "maxsplit",
     "memcpy",
+    "Merkle-Damgård",
     "MODINIT",
     "napi",
     "nargsf",
     "ndim",
+    "Needleman",
     "newfunc",
     "NOARGS",
+    "noexcept",
     "NOMINMAX",
     "NOTIMPLEMENTED",
+    "npos",
     "numpy",
     "octogram",
     "pytest",
     "Pythonic",
     "quadgram",
+    "Raita",
     "readlines",
     "releasebuffer",
+    "rfind",
     "richcompare",
+    "Ritchie",
+    "rmatcher",
+    "rmatches",
+    "rpartition",
+    "rsplit",
+    "rsplits",
+    "rstrip",
     "SIMD",
     "splitlines",
+    "ssize",
     "startswith",
+    "STL",
     "stringzilla",
     "Strs",
     "strzl",
     "substr",
     "SWAR",
+    "Tanimoto",
+    "thyrotropin",
+    "Titin",
     "TPFLAGS",
     "unigram",
+    "usecases",
     "Vardanian",
     "vectorcallfunc",
+    "Wagner",
+    "whitespaces",
+    "Wunsch",
     "XDECREF",
     "Zilla"
-  ]
+  ],
+  "editor.formatOnSave": true,
+  "editor.rulers": [
+    120
+  ],
+  "files.associations": {
+    "*.tcc": "cpp",
+    "__bit_reference": "cpp",
+    "__bits": "cpp",
+    "__config": "cpp",
+    "__debug": "cpp",
+    "__errc": "cpp",
+    "__functional_base": "cpp",
+    "__hash_table": "cpp",
+    "__locale": "cpp",
+    "__memory": "c",
+    "__mutex_base": "cpp",
+    "__node_handle": "cpp",
+    "__nullptr": "cpp",
+    "__split_buffer": "cpp",
+    "__string": "cpp",
+    "__threading_support": "cpp",
+    "__tree": "cpp",
+    "__tuple": "cpp",
+    "__verbose_abort": "cpp",
+    "algorithm": "cpp",
+    "any": "cpp",
+    "array": "cpp",
+    "atomic": "cpp",
+    "bit": "cpp",
+    "bitset": "cpp",
+    "cctype": "cpp",
+    "charconv": "c",
+    "chrono": "cpp",
+    "cinttypes": "cpp",
+    "clocale": "cpp",
+    "cmath": "cpp",
+    "codecvt": "cpp",
+    "compare": "cpp",
+    "complex": "cpp",
+    "concepts": "cpp",
+    "condition_variable": "cpp",
+    "csignal": "cpp",
+    "cstdarg": "cpp",
+    "cstddef": "cpp",
+    "cstdint": "cpp",
+    "cstdio": "cpp",
+    "cstdlib": "cpp",
+    "cstring": "cpp",
+    "ctime": "cpp",
+    "cwchar": "cpp",
+    "cwctype": "cpp",
+    "deque": "cpp",
+    "exception": "cpp",
+    "filesystem": "cpp",
+    "format": "cpp",
+    "forward_list": "cpp",
+    "fstream": "cpp",
+    "functional": "cpp",
+    "future": "cpp",
+    "initializer_list": "cpp",
+    "iomanip": "cpp",
+    "ios": "cpp",
+    "iosfwd": "cpp",
+    "iostream": "cpp",
+    "istream": "cpp",
+    "iterator": "cpp",
+    "limits": "cpp",
+    "list": "cpp",
+    "locale": "cpp",
+    "map": "cpp",
+    "memory": "cpp",
+    "memory_resource": "cpp",
+    "mutex": "cpp",
+    "new": "cpp",
+    "numbers": "cpp",
+    "numeric": "cpp",
+    "optional": "cpp",
+    "ostream": "cpp",
+    "queue": "cpp",
+    "random": "cpp",
+    "ratio": "cpp",
+    "semaphore": "cpp",
+    "set": "cpp",
+    "shared_mutex": "cpp",
+    "source_location": "cpp",
+    "span": "cpp",
+    "sstream": "cpp",
+    "stack": "cpp",
+    "stdexcept": "cpp",
+    "stop_token": "cpp",
+    "streambuf": "cpp",
+    "string": "cpp",
+    "string_view": "cpp",
+    "stringzilla.h": "c",
+    "strstream": "cpp",
+    "system_error": "cpp",
+    "thread": "cpp",
+    "tuple": "cpp",
+    "type_traits": "cpp",
+    "typeindex": "cpp",
+    "typeinfo": "cpp",
+    "unordered_map": "cpp",
+    "unordered_set": "cpp",
+    "utility": "cpp",
+    "variant": "cpp",
+    "vector": "cpp",
+    "stddef.h": "c",
+    "immintrin.h": "c"
+  },
+  "python.pythonPath": "~/miniconda3/bin/python"
 }
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 7700cb98..dfb3e46d 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,33 +2,39 @@
     "version": "2.0.0",
     "tasks": [
         {
-            "label": "Linux Build C++ Test Debug",
-            "command": "cmake -DSTRINGZILLA_BUILD_TEST=1 -DCMAKE_CXX_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_BUILD_TYPE=Debug -B ./build_debug && make -C ./build_debug",
-            "args": [],
+            "label": "Build with GCC: Debug",
+            "command": "./.github/workflows/build_tools.sh Debug GCC",
             "type": "shell",
             "problemMatcher": [
                 "$gcc"
             ]
         },
         {
-            "label": "Linux Build C++ Test Release",
-            "command": "cmake -DSTRINGZILLA_BUILD_TEST=1 -DCMAKE_CXX_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_BUILD_TYPE=RelWithDebInfo -B ./build_release && make -C ./build_release",
-            "args": [],
+            "label": "Build with GCC: Release",
+            "command": "./.github/workflows/build_tools.sh Release GCC",
             "type": "shell",
             "problemMatcher": [
                 "$gcc"
             ]
         },
         {
-            "label": "MacOS Build C++ Test Debug",
-            "command": "cmake -DSTRINGZILLA_BUILD_TEST=1 -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++ -DCMAKE_BUILD_TYPE=Debug -B ./build_debug && make -C ./build_debug",
-            "args": [],
-            "type": "shell",
+            "label": "Build with LLVM: Debug",
+            "command": "./.github/workflows/build_tools.sh Debug LLVM",
+            "type": "shell"
+        },
+        {
+            "label": "Build with LLVM: Release",
+            "command": "./.github/workflows/build_tools.sh Release LLVM",
+            "type": "shell"
+        },
+        {
+            "label": "Build with MSVC: Debug",
+            "command": "./.github/workflows/build_tools.sh Debug MSVC",
+            "type": "shell"
         },
         {
-            "label": "MacOS Build C++ Test Release",
-            "command": "cmake -DSTRINGZILLA_BUILD_TEST=1 -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -B ./build_release && make -C ./build_release",
-            "args": [],
+            "label": "Build with MSVC: Release",
+            "command": "./.github/workflows/build_tools.sh Release MSVC",
             "type": "shell"
         }
     ]
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 230c2a06..92891736 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,32 +1,59 @@
-# This CMake file is heavily inspired by following `stringzilla` CMake:
-# https://github.com/nlohmann/json/blob/develop/CMakeLists.txt
 cmake_minimum_required(VERSION 3.1)
 project(
   stringzilla
-  VERSION 0.1.0
-  LANGUAGES C CXX)
+  VERSION 2.0.4
+  LANGUAGES C CXX
+  DESCRIPTION "SIMD-accelerated string search, sort, hashes, fingerprints, & edit distances"
+  HOMEPAGE_URL "https://github.com/ashvardanian/stringzilla")
 
-set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD 99)
 set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_C_EXTENSIONS OFF)
+set(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_COMPILE_WARNING_AS_ERROR)
+set(DEV_USER_NAME $ENV{USER})
 
-# Determine if USearch is built as a subproject (using `add_subdirectory`) or if
-# it is the main project
+message(STATUS "C Compiler ID: ${CMAKE_C_COMPILER_ID}")
+message(STATUS "C Compiler Version: ${CMAKE_C_COMPILER_VERSION}")
+message(STATUS "C Compiler: ${CMAKE_C_COMPILER}")
+message(STATUS "C++ Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
+message(STATUS "C++ Compiler Version: ${CMAKE_CXX_COMPILER_VERSION}")
+message(STATUS "C++ Compiler: ${CMAKE_CXX_COMPILER}")
+message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+
+# Set a default build type to "Release" if none was specified
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  message(STATUS "Setting build type to 'Release' as none was specified.")
+  set(CMAKE_BUILD_TYPE
+    Release
+    CACHE STRING "Choose the type of build." FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
+    "MinSizeRel" "RelWithDebInfo")
+endif()
+
+# Determine if StringZilla is built as a subproject (using `add_subdirectory`)
+# or if it is the main project
 set(STRINGZILLA_IS_MAIN_PROJECT OFF)
+
 if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
   set(STRINGZILLA_IS_MAIN_PROJECT ON)
 endif()
 
-# Options
+# Installation options
 option(STRINGZILLA_INSTALL "Install CMake targets" OFF)
 option(STRINGZILLA_BUILD_TEST "Compile a native unit test in C++"
-       ${STRINGZILLA_IS_MAIN_PROJECT})
+  ${STRINGZILLA_IS_MAIN_PROJECT})
 option(STRINGZILLA_BUILD_BENCHMARK "Compile a native benchmark in C++"
-       ${STRINGZILLA_IS_MAIN_PROJECT})
-option(STRINGZILLA_BUILD_WOLFRAM "Compile Wolfram Language bindings" OFF)
+  ${STRINGZILLA_IS_MAIN_PROJECT})
+option(STRINGZILLA_BUILD_SHARED "Compile a dynamic library" ${STRINGZILLA_IS_MAIN_PROJECT})
+set(STRINGZILLA_TARGET_ARCH
+  ""
+  CACHE STRING "Architecture to tell the compiler to optimize for (-march)")
 
 # Includes
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
 include(ExternalProject)
+include(CheckCSourceCompiles)
 
 # Allow CMake 3.13+ to override options when using FetchContent /
 # add_subdirectory
@@ -37,73 +64,181 @@ endif()
 # Configuration
 include(GNUInstallDirs)
 set(STRINGZILLA_TARGET_NAME ${PROJECT_NAME})
-set(STRINGZILLA_CONFIG_INSTALL_DIR
-    "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}"
-    CACHE INTERNAL "")
-set(STRINGZILLA_INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_INCLUDEDIR}")
-set(STRINGZILLA_TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
-set(STRINGZILLA_CMAKE_CONFIG_TEMPLATE "cmake/config.cmake.in")
-set(STRINGZILLA_CMAKE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}")
-set(STRINGZILLA_CMAKE_VERSION_CONFIG_FILE
-    "${STRINGZILLA_CMAKE_CONFIG_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
-set(STRINGZILLA_CMAKE_PROJECT_CONFIG_FILE
-    "${STRINGZILLA_CMAKE_CONFIG_DIR}/${PROJECT_NAME}Config.cmake")
-set(STRINGZILLA_CMAKE_PROJECT_TARGETS_FILE
-    "${STRINGZILLA_CMAKE_CONFIG_DIR}/${PROJECT_NAME}Targets.cmake")
-set(STRINGZILLA_PKGCONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/pkgconfig")
-
-# Define our header-only library
-add_library(${STRINGZILLA_TARGET_NAME} INTERFACE)
-add_library(${PROJECT_NAME}::${STRINGZILLA_TARGET_NAME} ALIAS
-            ${STRINGZILLA_TARGET_NAME})
 set(STRINGZILLA_INCLUDE_BUILD_DIR "${PROJECT_SOURCE_DIR}/include/")
 
-target_compile_definitions(
-  ${STRINGZILLA_TARGET_NAME}
-  INTERFACE $<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:STRINGZILLA_USE_OPENMP=0>)
+# Define our library
+add_library(${STRINGZILLA_TARGET_NAME} INTERFACE)
+add_library(${PROJECT_NAME}::${STRINGZILLA_TARGET_NAME} ALIAS ${STRINGZILLA_TARGET_NAME})
+
 target_include_directories(
-  ${STRINGZILLA_TARGET_NAME} ${STRINGZILLA_SYSTEM_INCLUDE}
+  ${STRINGZILLA_TARGET_NAME}
   INTERFACE $<BUILD_INTERFACE:${STRINGZILLA_INCLUDE_BUILD_DIR}>
-            $<INSTALL_INTERFACE:include>)
+  $<INSTALL_INTERFACE:include>)
 
 if(STRINGZILLA_INSTALL)
-  install(DIRECTORY ${STRINGZILLA_INCLUDE_BUILD_DIR}
-          DESTINATION ${STRINGZILLA_INCLUDE_INSTALL_DIR})
-  install(FILES ${STRINGZILLA_CMAKE_PROJECT_CONFIG_FILE}
-                ${STRINGZILLA_CMAKE_VERSION_CONFIG_FILE}
-          DESTINATION ${STRINGZILLA_CONFIG_INSTALL_DIR})
-  export(
-    TARGETS ${STRINGZILLA_TARGET_NAME}
-    NAMESPACE ${PROJECT_NAME}::
-    FILE ${STRINGZILLA_CMAKE_PROJECT_TARGETS_FILE})
   install(
     TARGETS ${STRINGZILLA_TARGET_NAME}
     EXPORT ${STRINGZILLA_TARGETS_EXPORT_NAME}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
     INCLUDES
     DESTINATION ${STRINGZILLA_INCLUDE_INSTALL_DIR})
-  install(
-    EXPORT ${STRINGZILLA_TARGETS_EXPORT_NAME}
-    NAMESPACE ${PROJECT_NAME}::
-    DESTINATION ${STRINGZILLA_CONFIG_INSTALL_DIR})
-  install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
-          DESTINATION ${STRINGZILLA_PKGCONFIG_INSTALL_DIR})
+  install(DIRECTORY ${STRINGZILLA_INCLUDE_BUILD_DIR}
+    DESTINATION ${STRINGZILLA_INCLUDE_INSTALL_DIR})
 endif()
 
-if(${STRINGZILLA_BUILD_TEST} OR ${STRINGZILLA_BUILD_BENCHMARK})
-  add_executable(stringzilla_test scripts/test.cpp)
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
-  set(CMAKE_CXX_FLAGS
-      "${CMAKE_CXX_FLAGS} -O3 -flto -march=native -finline-functions -funroll-loops"
+if(${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13)
+  include(CTest)
+  enable_testing()
+endif()
+
+# Function to set compiler-specific flags
+function(set_compiler_flags target cpp_standard target_arch)
+  target_include_directories(${target} PRIVATE scripts)
+  target_link_libraries(${target} PRIVATE ${STRINGZILLA_TARGET_NAME})
+
+  # Set output directory for single-configuration generators (like Make)
+  set_target_properties(${target} PROPERTIES
+    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>
   )
 
-  target_include_directories(stringzilla_test PRIVATE stringzilla)
-  set_target_properties(stringzilla_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY
-                                                    ${CMAKE_BINARY_DIR})
+  # Set output directory for multi-configuration generators (like Visual Studio)
+  foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES)
+    string(TOUPPER ${config} config_upper)
+    set_target_properties(${target} PROPERTIES
+      RUNTIME_OUTPUT_DIRECTORY_${config_upper} ${CMAKE_BINARY_DIR}/$<0:>
+    )
+  endforeach()
 
-  if(${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER
-                                            3.13)
-    include(CTest)
-    enable_testing()
-    add_test(NAME stringzilla_test COMMAND stringzilla_test)
+  # Set the C++ standard
+  if(NOT ${cpp_standard} STREQUAL "")
+    # Use the /Zc:__cplusplus flag to correctly define the __cplusplus macro in MSVC
+    set(CXX_STANDARD_MSVC "/std:c++${cpp_standard};/Zc:__cplusplus")
+    set(CXX_STANDARD_GNU "-std=c++${cpp_standard}")
+    target_compile_options(${target} PRIVATE
+      "$<$<CXX_COMPILER_ID:MSVC>:${CXX_STANDARD_MSVC}>"
+      "$<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:${CXX_STANDARD_GNU}>"
+    )
   endif()
+
+  # Maximum warnings level & warnings as error
+  target_compile_options(
+    ${target}
+    PRIVATE
+    "$<$<CXX_COMPILER_ID:MSVC>:/STOP>" # For MSVC, /WX would have been sufficient
+    "$<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra;-pedantic;-Werror;-Wfatal-errors;-Wno-unknown-pragmas;-Wno-cast-function-type;-Wno-unused-function>"
+    "$<$<CXX_COMPILER_ID:Clang>:-Wall;-Wextra;-pedantic;-Werror;-Wfatal-errors;-Wno-unknown-pragmas>"
+    "$<$<CXX_COMPILER_ID:AppleClang>:-Wall;-Wextra;-pedantic;-Werror;-Wfatal-errors;-Wno-unknown-pragmas>"
+  )
+
+  # Set optimization options for different compilers differently
+  target_compile_options(
+    ${target}
+    PRIVATE
+    "$<$<AND:$<CXX_COMPILER_ID:GNU>,$<OR:$<CONFIG:Release>,$<CONFIG:RelWithDebInfo>>>:-O3>"
+    "$<$<AND:$<CXX_COMPILER_ID:GNU>,$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>>:-g>"
+    "$<$<AND:$<CXX_COMPILER_ID:Clang>,$<OR:$<CONFIG:Release>,$<CONFIG:RelWithDebInfo>>>:-O3>"
+    "$<$<AND:$<CXX_COMPILER_ID:Clang>,$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>>:-g>"
+    "$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<CONFIG:Release>>:/O2>"
+    "$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<OR:$<CONFIG:Release>,$<CONFIG:RelWithDebInfo>>>:/O2>"
+    "$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>>:/Zi>"
+  )
+
+  # Enable Position Independent Code
+  target_compile_options(${target} PRIVATE "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-fPIC>")
+  target_link_options(${target} PRIVATE "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-fPIC>")
+
+  # Check for ${target_arch} and set it or use "march=native" if not defined
+  if("${target_arch}" STREQUAL "")
+    # MSVC does not have a direct equivalent to -march=native
+    target_compile_options(
+      ${target} PRIVATE
+      "$<$<AND:$<CXX_COMPILER_ID:GNU>,$<NOT:$<CXX_COMPILER_ID:AppleClang>>>:-march=native>"
+      "$<$<CXX_COMPILER_ID:MSVC>:/arch:AVX2>")
+  else()
+    target_compile_options(
+      ${target}
+      PRIVATE
+      "$<$<AND:$<CXX_COMPILER_ID:GNU>,$<NOT:$<CXX_COMPILER_ID:AppleClang>>>:-march=${target_arch}>"
+      "$<$<CXX_COMPILER_ID:MSVC>:/arch:${target_arch}>")
+  endif()
+
+  # Sanitizer options for Debug mode
+  if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+    target_compile_options(
+      ${target}
+      PRIVATE
+      "$<$<CXX_COMPILER_ID:GNU,Clang>:-fsanitize=address;-fsanitize=leak>"
+      "$<$<CXX_COMPILER_ID:MSVC>:/fsanitize=address>")
+
+    target_link_options(
+      ${target}
+      PRIVATE
+      "$<$<CXX_COMPILER_ID:GNU,Clang>:-fsanitize=address;-fsanitize=leak>"
+      "$<$<CXX_COMPILER_ID:MSVC>:/fsanitize=address>")
+
+    # Define SZ_DEBUG macro based on build configuration
+    target_compile_definitions(
+      ${target}
+      PRIVATE
+      "$<$<CONFIG:Debug>:SZ_DEBUG=1>"
+      "$<$<NOT:$<CONFIG:Debug>>:SZ_DEBUG=0>"
+    )
+  endif()
+endfunction()
+
+function(define_launcher exec_name source cpp_standard target_arch)
+  add_executable(${exec_name} ${source})
+  set_compiler_flags(${exec_name} ${cpp_standard} "${target_arch}")
+  add_test(NAME ${exec_name} COMMAND ${exec_name})
+endfunction()
+
+if(${STRINGZILLA_BUILD_BENCHMARK})
+  define_launcher(stringzilla_bench_search scripts/bench_search.cpp 17 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_bench_similarity scripts/bench_similarity.cpp 17 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_bench_sort scripts/bench_sort.cpp 17 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_bench_token scripts/bench_token.cpp 17 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_bench_container scripts/bench_container.cpp 17 "${STRINGZILLA_TARGET_ARCH}")
 endif()
+
+if(${STRINGZILLA_BUILD_TEST})
+  # Make sure that the compilation passes for different C++ standards
+  # ! Keep in mind, MSVC only supports C++11 and newer.
+  define_launcher(stringzilla_test_cpp11 scripts/test.cpp 11 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_test_cpp14 scripts/test.cpp 14 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_test_cpp17 scripts/test.cpp 17 "${STRINGZILLA_TARGET_ARCH}")
+  define_launcher(stringzilla_test_cpp20 scripts/test.cpp 20 "${STRINGZILLA_TARGET_ARCH}")
+
+  # Check system architecture to avoid complex cross-compilation workflows, but
+  # compile multiple backends: disabling all SIMD, enabling only AVX2, only AVX-512, only Arm Neon.
+  if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64")
+    # x86 specific backends
+    define_launcher(stringzilla_test_cpp20_x86_serial scripts/test.cpp 20 "ivybridge")
+    define_launcher(stringzilla_test_cpp20_x86_avx2 scripts/test.cpp 20 "haswell")
+    define_launcher(stringzilla_test_cpp20_x86_avx512 scripts/test.cpp 20 "sapphirerapids")
+  elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|aarch64|AARCH64")
+    # ARM specific backends
+    define_launcher(stringzilla_test_cpp20_arm_serial scripts/test.cpp 20 "armv8-a")
+    define_launcher(stringzilla_test_cpp20_arm_neon scripts/test.cpp 20 "armv8-a+simd")
+  endif()
+endif()
+
+if(${STRINGZILLA_BUILD_SHARED})
+  add_library(stringzilla_shared SHARED c/lib.c)
+  set_compiler_flags(stringzilla_shared "" "${STRINGZILLA_TARGET_ARCH}")
+  set_target_properties(stringzilla_shared PROPERTIES
+    VERSION ${PROJECT_VERSION}
+    SOVERSION 1
+    POSITION_INDEPENDENT_CODE ON
+    PUBLIC_HEADER include/stringzilla/stringzilla.h)
+
+  # Try compiling a version without linking the LibC
+  add_library(stringzillite SHARED c/lib.c)
+  set_compiler_flags(stringzillite "" "${STRINGZILLA_TARGET_ARCH}")
+  target_compile_definitions(stringzillite PRIVATE "SZ_AVOID_LIBC=1")
+  set_target_properties(stringzillite PROPERTIES
+    VERSION ${PROJECT_VERSION}
+    SOVERSION 1
+    POSITION_INDEPENDENT_CODE ON
+    PUBLIC_HEADER include/stringzilla/stringzilla.h)
+endif()
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..bace0dc1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,300 @@
+# Contributing to StringZilla
+
+Thank you for coming here! It's always nice to have third-party contributors 🤗
+Depending on the type of contribution, you may need to follow different steps.
+
+---
+
+## Project Structure
+
+The project is split into the following parts:
+
+- `include/stringzilla/stringzilla.h` - single-header C implementation.
+- `include/stringzilla/stringzilla.hpp` - single-header C++ wrapper.
+- `python/*` - Python bindings.
+- `javascript/*` - JavaScript bindings.
+- `scripts/*` - Scripts for benchmarking and testing.
+
+For minimal test coverage, check the following scripts:
+
+- `test.cpp` - tests C++ API (not underlying C) against STL.
+- `test.py` - tests Python API against native strings.
+- `test.js`.
+
+At the C++ level all benchmarks also validate the results against the STL baseline, serving as tests on real-world data.
+They have the broadest coverage of the library, and are the most important to keep up-to-date:
+
+- `bench_token.cpp` - token-level ops, like hashing, ordering, equality checks.
+- `bench_search.cpp` - bidirectional substring search, both exact and fuzzy.
+- `bench_similarity.cpp` - benchmark all edit distance backends.
+- `bench_sort.cpp` - sorting, partitioning, merging.
+- `bench_container.cpp` - STL containers with different string keys.
+
+The role of Python benchmarks is less to provide absolute number, but to compare against popular tools in the Python ecosystem.
+
+- `bench_search.(py|ipynb)` - compares against native Python `str`.
+- `bench_sort.(py|ipynb)` - compares against `pandas`.
+- `bench_similarity.(ipynb)` - compares against `jellyfish`, `editdistance`, etc.
+
+## IDE Integrations
+
+The project was originally developed in VS Code, and contains a set of configuration files for that IDE under `.vscode/`.
+
+- `tasks.json` - build tasks for CMake.
+- `launch.json` - debugger launchers for CMake.
+- `extensions.json` - recommended extensions for VS Code, including:
+    - `ms-vscode.cpptools-themes` - C++ language support.
+    - `ms-vscode.cmake-tools`, `cheshirekow.cmake-format` - CMake integration.
+    - `ms-python.python`, `ms-python.black-formatter` - Python language support.
+    - `yzhang.markdown-all-in-one` - formatting Markdown.
+    - `aaron-bond.better-comments` - color-coded comments.
+
+## Code Styling
+
+The project uses `.clang-format` to enforce a consistent code style.
+Modern IDEs, like VS Code, can be configured to automatically format the code on save.
+
+- East const over const West. Write `char const*` instead of `const char*`.
+- For color-coded comments start the line with `!` for warnings or `?` for questions.
+- Sort the includes: standard libraries, third-party libraries, and only then internal project headers.
+
+For C++ code:
+
+- Explicitly use `std::` or `sz::` namespaces over global `memcpy`, `uint64_t`, etc.
+- Explicitly mark `noexcept` or `noexcept(false)` for all library interfaces.
+- Document all possible exceptions of an interface using `@throw` in Doxygen.
+- Avoid C-style variadic arguments in favor of templates.
+- Avoid C-style casts in favor of `static_cast`, `reinterpret_cast`, and `const_cast`, except for places where a C function is called.
+- Use lower-case names for everything, except settings/conditions macros. Function-like macros, that take arguments, should be lowercase as well.
+- In templates prefer `typename` over `class`.
+- Prepend "private" symbols with `_` underscore.
+
+For Python code:
+
+- Use lower-case names for functions and variables.
+
+## Contributing in C++ and C
+
+The primary C implementation and the C++ wrapper are built with CMake.
+Assuming the extensive use of new SIMD intrinsics and recent C++ language features, using a recent compiler is recommended.
+We prefer GCC 12, which is available from default Ubuntu repositories with Ubuntu 22.04 LTS onwards.
+If this is your first experience with CMake, use the following commands to get started:
+
+```bash
+sudo apt-get update && sudo apt-get install cmake build-essential libjemalloc-dev g++-12 gcc-12 # Ubuntu
+brew install libomp llvm # MacOS
+```
+
+Using modern syntax, this is how you build and run the test suite:
+
+```bash
+cmake -DSTRINGZILLA_BUILD_TEST=1 -B build_debug
+cmake --build ./build_debug --config Debug          # Which will produce the following targets:
+./build_debug/stringzilla_test_cpp20                # Unit test for the entire library compiled for current hardware
+./build_debug/stringzilla_test_cpp20_x86_serial     # x86 variant compiled for IvyBridge - last arch. before AVX2
+./build_debug/stringzilla_test_cpp20_arm_serial     # Arm variant compiled without Neon
+```
+
+For benchmarks, you can use the following commands:
+
+```bash
+cmake -DSTRINGZILLA_BUILD_BENCHMARK=1 -B build_release
+cmake --build ./build_release --config Release      # Which will produce the following targets:
+./build_release/stringzilla_bench_search <path>     # for substring search
+./build_release/stringzilla_bench_token <path>      # for hashing, equality comparisons, etc.
+./build_release/stringzilla_bench_similarity <path> # for edit distances and alignment scores
+./build_release/stringzilla_bench_sort <path>       # for sorting arrays of strings
+./build_release/stringzilla_bench_container <path>  # for STL containers with string keys
+```
+
+You may want to download some datasets for benchmarks, like these:
+
+```sh
+# English Leipzig Corpora Collection
+# 124 MB, 1'000'000 lines of ASCII, 8'388'608 tokens of mean length 5
+wget --no-clobber -O leipzig1M.txt https://introcs.cs.princeton.edu/python/42sort/leipzig1m.txt 
+
+# Hutter Prize "enwik9" dataset for compression
+# 1 GB (0.3 GB compressed), 13'147'025 lines of ASCII, 67'108'864 tokens of mean length 6
+wget --no-clobber -O enwik9.zip http://mattmahoney.net/dc/enwik9.zip
+unzip enwik9.zip && rm enwik9.zip && mv enwik9 enwik9.txt
+
+# XL Sum dataset for multilingual extractive summarization
+# 4.7 GB (1.7 GB compressed), 1'004'598 lines of UTF8, 268'435'456 tokens of mean length 8
+wget --no-clobber -O xlsum.csv.gz https://github.com/ashvardanian/xl-sum/releases/download/v1.0.0/xlsum.csv.gz
+gzip -d xlsum.csv.gz
+```
+
+Running on modern hardware, you may want to compile the code for older generations to compare the relative performance.
+The assumption would be that newer ISA extensions would provide better performance.
+On x86_64, you can use the following commands to compile for Sandy Bridge, Haswell, and Sapphire Rapids:
+
+```bash
+cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+    -DSTRINGZILLA_TARGET_ARCH="ivybridge" -B build_release/ivybridge && \
+    cmake --build build_release/ivybridge --config Release
+cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+    -DSTRINGZILLA_TARGET_ARCH="haswell" -B build_release/haswell && \
+    cmake --build build_release/haswell --config Release
+cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+    -DSTRINGZILLA_TARGET_ARCH="sapphirerapids" -B build_release/sapphirerapids && \
+    cmake --build build_release/sapphirerapids --config Release
+```
+
+Alternatively, you may want to compare the performance of the code compiled with different compilers.
+On x86_64, you may want to compare GCC, Clang, and ICX.
+
+```bash
+cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 -DSTRINGZILLA_BUILD_SHARED=1 \
+    -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 \
+    -B build_release/gcc && cmake --build build_release/gcc --config Release
+cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 -DSTRINGZILLA_BUILD_SHARED=1 \
+    -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_C_COMPILER=clang-14 \
+    -B build_release/clang && cmake --build build_release/clang --config Release
+```
+
+To use CppCheck for static analysis make sure to export the compilation commands.
+Overall, CppCheck and Clang-Tidy are extremely noisy and not suitable for CI, but may be useful for local development.
+
+```bash
+sudo apt install cppcheck clang-tidy-11
+
+cmake -B build_artifacts \
+  -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+  -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+  -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+  -DSTRINGZILLA_BUILD_TEST=1
+
+cppcheck --project=build_artifacts/compile_commands.json --enable=all
+
+clang-tidy-11 -p build_artifacts
+```
+
+To simplify tracing and profiling, build with symbols using the `RelWithDebInfo` configuration.
+Here is an example for profiling one target - `stringzilla_bench_token`.
+
+```bash
+cmake -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+    -DSTRINGZILLA_BUILD_TEST=1 \
+    -DSTRINGZILLA_BUILD_SHARED=1 \
+    -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+    -B build_profile
+cmake --build ./build_profile --config Release --target stringzilla_bench_token
+
+# Check that the debugging symbols are there with your favorite tool
+readelf --sections ./build_profile/stringzilla_bench_token | grep debug
+objdump -h ./build_profile/stringzilla_bench_token | grep debug
+
+# Profile
+sudo perf record -g ./build_profile/stringzilla_bench_token ./leipzig1M.txt
+sudo perf report
+```
+
+## Contributing in Python
+
+Python bindings are implemented using pure CPython, so you wouldn't need to install SWIG, PyBind11, or any other third-party library.
+
+```bash
+pip install -e . # To build locally from source
+```
+
+For testing we use PyTest, which may not be installed on your system.
+
+```bash
+pip install pytest              # To install PyTest
+pytest scripts/test.py -s -x    # To run the test suite
+```
+
+For fuzzing we love the ability to call the native C implementation from Python bypassing the binding layer.
+For that we use Cppyy, derived from Cling, a Clang-based C++ interpreter.
+
+```bash
+pip install cppyy                       # To install Cppyy
+python scripts/similarity_fuzz.py       # To run the fuzzing script
+```
+
+For benchmarking, the following scripts are provided.
+
+```sh
+python scripts/bench_search.py --haystack_path "your file" --needle "your pattern" # real data
+python scripts/bench_search.py --haystack_pattern "abcd" --haystack_length 1e9 --needle "abce" # synthetic data
+python scripts/similarity_bench.py --text_path "your file" # edit ditance computations
+```
+
+Before you ship, please make sure the packaging works.
+
+```bash
+cibuildwheel --platform linux
+```
+
+## Contributing in JavaScript
+
+```bash
+npm ci && npm test
+```
+
+## Contributing in Swift
+
+```bash
+swift build && swift test
+```
+
+Running Swift on Linux requires a couple of extra steps, as the Swift compiler is not available in the default repositories.
+Please get the most recent Swift tarball from the [official website](https://www.swift.org/install/).
+At the time of writing, for 64-bit Arm CPU running Ubuntu 22.04, the following commands would work:
+
+```bash
+wget https://download.swift.org/swift-5.9.2-release/ubuntu2204-aarch64/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz
+tar xzf swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz
+sudo mv swift-5.9.2-RELEASE-ubuntu22.04-aarch64 /usr/share/swift
+echo "export PATH=/usr/share/swift/usr/bin:$PATH" >> ~/.bashrc
+source ~/.bashrc
+```
+
+Alternatively, on Linux, the official Swift Docker image can be used for builds and tests:
+
+```bash
+sudo docker run --rm -v "$PWD:/workspace" -w /workspace swift:5.9 /bin/bash -cl "swift build -c release --static-swift-stdlib && swift test -c release --enable-test-discovery"
+```
+
+## General Performance Observations
+
+### Unaligned Loads
+
+One common surface of attack for performance optimizations is minimizing unaligned loads.
+Such solutions are beautiful from the algorithmic perspective, but often lead to worse performance.
+It's often cheaper to issue two interleaving wide-register loads, than try minimizing those loads at the cost of juggling registers.
+
+### Register Pressure
+
+Byte-level comparisons are simpler and often faster, than n-gram comparisons with subsequent interleaving.
+In the following example we search for 4-byte needles in a haystack, loading at different offsets, and comparing then as arrays of 32-bit integers.
+
+```c
+h0_vec.zmm = _mm512_loadu_epi8(h);
+h1_vec.zmm = _mm512_loadu_epi8(h + 1);
+h2_vec.zmm = _mm512_loadu_epi8(h + 2);
+h3_vec.zmm = _mm512_loadu_epi8(h + 3);
+matches0 = _mm512_cmpeq_epi32_mask(h0_vec.zmm, n_vec.zmm);
+matches1 = _mm512_cmpeq_epi32_mask(h1_vec.zmm, n_vec.zmm);
+matches2 = _mm512_cmpeq_epi32_mask(h2_vec.zmm, n_vec.zmm);
+matches3 = _mm512_cmpeq_epi32_mask(h3_vec.zmm, n_vec.zmm);
+if (matches0 | matches1 | matches2 | matches3)
+    return h + sz_u64_ctz(_pdep_u64(matches0, 0x1111111111111111) | //
+                          _pdep_u64(matches1, 0x2222222222222222) | //
+                          _pdep_u64(matches2, 0x4444444444444444) | //
+                          _pdep_u64(matches3, 0x8888888888888888));
+```
+
+A simpler solution would be to compare byte-by-byte, but in that case we would need to populate multiple registers, broadcasting different letters of the needle into them.
+That may not be noticeable on a micro-benchmark, but it would be noticeable on real-world workloads, where the CPU will speculatively interleave those search operations with something else happening in that context.
+
+## Working on Alternative Hardware Backends
+
+## Working on Faster Edit Distances
+
+## Working on Random String Generators
+
+## Working on Sequence Processing and Sorting
+
+
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 00000000..64ea4388
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,25 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "stringzilla"
+version = "2.0.4"
+dependencies = [
+ "cc",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 00000000..cb2be7cc
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "stringzilla"
+version = "2.0.4"
+authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
+description = "Faster SIMD-accelerated string search, sorting, fingerprints, and edit distances"
+edition = "2021"
+license = "Apache-2.0"
+publish = true
+repository = "https://github.com/ashvardanian/stringzilla"
+
+[lib]
+name = "stringzilla"
+path = "rust/lib.rs"
+
+[build-dependencies]
+cc = "1.0"
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 00000000..27d90276
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,40 @@
+// swift-tools-version:5.9
+import PackageDescription
+
+let package = Package(
+    name: "StringZilla",
+    products: [
+        .library(
+            name: "StringZilla",
+            targets: ["StringZillaC", "StringZilla"]
+        )
+    ],
+    targets: [
+        .target(
+            name: "StringZillaC",
+            path: "include/stringzilla", // Adjust the path to include your C source files
+            sources: ["../../c/lib.c"], // Include the source file here
+            publicHeadersPath: ".",
+            cSettings: [
+                .define("SZ_DYNAMIC_DISPATCH", to: "1"), // Define a macro
+                .define("SZ_AVOID_LIBC", to: "0"), // We need `malloc` from LibC
+                .define("SZ_DEBUG", to: "0"), // We don't need any extra assertions in the C layer
+                .headerSearchPath("include/stringzilla"), // Specify header search paths
+                .unsafeFlags(["-Wall"]) // Use with caution: specify custom compiler flags
+            ]
+        ),
+        .target(
+            name: "StringZilla",
+            dependencies: ["StringZillaC"],
+            path: "swift",
+            exclude: ["Test.swift"]
+        ),
+        .testTarget(
+            name: "StringZillaTests",
+            dependencies: ["StringZilla"],
+            path: "swift",
+            sources: ["Test.swift"]
+        )
+    ],
+    cLanguageStandard: CLanguageStandard.c99
+)
diff --git a/README.md b/README.md
index b6c8b363..431888b6 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,280 @@
 # StringZilla 🦖
 
-StringZilla is the Godzilla of string libraries, splitting, sorting, and shuffling large textual datasets faster than you can say "Tokyo Tower" 😅
-
-- ✅ Single-header pure C 99 implementation [docs](#quick-start-c-🛠️)
-- ✅ [Direct CPython bindings](https://ashvardanian.com/posts/pybind11-cpython-tutorial/) with minimal call latency [docs](#quick-start-python-🐍)
-- ✅ [SWAR](https://en.wikipedia.org/wiki/SWAR) and [SIMD](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) acceleration on x86 (AVX2) and ARM (NEON)
-- ✅ [Radix](https://en.wikipedia.org/wiki/Radix_sort)-like sorting faster than C++ `std::sort`
-- ✅ [Memory-mapping](https://en.wikipedia.org/wiki/Memory-mapped_file) to work with larger-than-RAM datasets
-- ✅ Memory-efficient compressed arrays to work with sequences
-- 🔜 JavaScript bindings are on their way.
-
-This library saved me tens of thousands of dollars pre-processing large datasets for machine learning, even on the scale of a single experiment.
-So if you want to process the 6 Billion images from [LAION](https://laion.ai/blog/laion-5b/), or the 250 Billion web pages from the [CommonCrawl](https://commoncrawl.org/), or even just a few million lines of server logs, and haunted by Python's `open(...).readlines()` and `str().splitlines()` taking forever, this should help 😊
-
-## Performance
-
-StringZilla is built on a very simple heuristic:
-
-> If the first 4 bytes of the string are the same, the strings are likely to be equal.
-> Similarly, the first 4 bytes of the strings can be used to determine their relative order most of the time.
-
-Thanks to that it can avoid scalar code processing one `char` at a time and use hyper-scalar code to achieve `memcpy` speeds.
-__The implementation fits into a single C 99 header file__ and uses different SIMD flavors and SWAR on older platforms.
-
-### Substring Search
-
-| Backend \ Device         |                    IoT |                   Laptop |                    Server |
-| :----------------------- | ---------------------: | -----------------------: | ------------------------: |
-| __Speed Comparison__ 🐇   |                        |                          |                           |
-| Python `for` loop        |                 4 MB/s |                  14 MB/s |                   11 MB/s |
-| C++ `for` loop           |               520 MB/s |                 1.0 GB/s |                  900 MB/s |
-| C++ `string.find`        |               560 MB/s |                 1.2 GB/s |                  1.3 GB/s |
-| Scalar StringZilla       |                 2 GB/s |                 3.3 GB/s |                  3.5 GB/s |
-| Hyper-Scalar StringZilla |           __4.3 GB/s__ |              __12 GB/s__ |             __12.1 GB/s__ |
-| __Efficiency Metrics__ 📊 |                        |                          |                           |
-| CPU Specs                | 8-core ARM, 0.5 W/core | 8-core Intel, 5.6 W/core | 22-core Intel, 6.3 W/core |
-| Performance/Core         |         2.1 - 3.3 GB/s |              __11 GB/s__ |                 10.5 GB/s |
-| Bytes/Joule              |           __4.2 GB/J__ |                   2 GB/J |                  1.6 GB/J |
-
-### Split, Partition, Sort, and Shuffle
-
-Coming soon.
+[![StringZilla Python installs](https://static.pepy.tech/personalized-badge/stringzilla?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=StringZilla%20Python%20installs)](https://github.com/ashvardanian/stringzilla)
+[![StringZilla Rust installs](https://img.shields.io/crates/d/stringzilla?logo=rust")](https://crates.io/crates/stringzilla)
+![StringZilla code size](https://img.shields.io/github/languages/code-size/ashvardanian/stringzilla)
+
+StringZilla is the GodZilla of string libraries, using [SIMD][faq-simd] and [SWAR][faq-swar] to accelerate string operations on modern CPUs.
+It is up to 10x faster than the default string libraries in C, C++, Python, and other languages, while covering broad functionality.
+Aside from exact search, the library also accelerates fuzzy string matching, edit distance computation, and sorting.
+For some languages, it also provides lazily-evaluated ranges, to avoid memory allocations, and even random-string generators.
+
+[faq-simd]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data
+[faq-swar]: https://en.wikipedia.org/wiki/SWAR
+
+- __[C](#quick-start-cc-🛠️) :__ Upgrade LibC's `<string.h>` to `<stringzilla.h>`  in C 99
+- __[C++](#basic-usage-with-c-11-and-newer):__ Upgrade STL's `<string>` to `<stringzilla.hpp>` in C++ 11
+- 🐍 __[Python](#quick-start-python-🐍):__ Upgrade your `str` to faster `Str`
+- 🍎 __[Swift](#quick-start-swift-🍏):__ Use the `String+StringZilla` extension
+- 🦀 __[Rust](#quick-start-rust-🦀):__ Use the `StringZilla` traits crate
+- 📚 Researcher? Jump to [Algorithms & Design Decisions](#algorithms--design-decisions-📚)
+- 🤝 Want to help? Jump to [Contributing](CONTRIBUTING.md)
+- Code in other languages? Let [me](https://github.com/ashvardanian) know!
+
+__Who is this for?__
+
+- For data-engineers parsing large datasets, like the [CommonCrawl](https://commoncrawl.org/), [RedPajama](https://github.com/togethercomputer/RedPajama-Data), or [LAION](https://laion.ai/blog/laion-5b/).
+- For software engineers optimizing strings in their apps and services.
+- For bioinformaticians and search engineers looking for edit-distances for [USearch](https://github.com/unum-cloud/usearch).
+- For [DBMS][faq-dbms] devs, optimizing `LIKE`, `ORDER BY`, and `GROUP BY` operations.
+- For hardware designers, needing a SWAR baseline for strings-processing functionality.
+- For students studying SIMD/SWAR applications to non-data-parallel operations.
+
+[faq-dbms]: https://en.wikipedia.org/wiki/Database
+
+## Throughput Benchmarks
+
+![StringZilla Cover](assets/cover-strinzilla.jpeg)
+
+StringZilla has a lot of functionality, most of which is covered by benchmarks across C, C++, Python and other languages.
+You can find those in the `./scripts` directory, with usage notes listed in the `CONTRIBUTING.md` file.
+Notably, if the CPU supports misaligned loads, even the 64-bit SWAR backends are faster than either standard library.
+
+<table style="width: 100%; text-align: center; table-layout: fixed;">
+  <colgroup>
+    <col style="width: 25%;">
+    <col style="width: 25%;">
+    <col style="width: 25%;">
+    <col style="width: 25%;">
+  </colgroup>
+  <tr>
+    <th align="center">LibC</th>
+    <th align="center">C++ Standard</th>
+    <th align="center">Python</th>
+    <th align="center">StringZilla</th>
+  </tr>
+  <!-- Substrings, normal order -->
+  <tr>
+    <td colspan="4" align="center">find the first occurrence of a random word from text, ≅ 5 bytes long</td>
+  </tr>
+  <tr>
+    <td align="center">
+      <code>strstr</code> <sup>1</sup><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>7.4</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>2.0</b> GB/s
+    </td>
+    <td align="center">
+      <code>.find</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>2.9</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>1.6</b> GB/s
+    </td>
+    <td align="center">
+      <code>.find</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>1.1</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.6</b> GB/s
+    </td>
+    <td align="center">
+      <code>sz_find</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>10.6</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>7.1</b> GB/s
+    </td>
+  </tr>
+  <!-- Substrings, reverse order -->
+  <tr>
+    <td colspan="4" align="center">find the last occurrence of a random word from text, ≅ 5 bytes long</td>
+  </tr>
+  <tr>
+    <td align="center">❌</td>
+    <td align="center">
+      <code>.rfind</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.5</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.4</b> GB/s
+    </td>
+    <td align="center">
+      <code>.rfind</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.9</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.5</b> GB/s
+    </td>
+    <td align="center">
+      <code>sz_rfind</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>10.8</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>6.7</b> GB/s
+    </td>
+  </tr>
+  <!-- Characters, normal order -->
+  <tr>
+    <td colspan="4" align="center">find the first occurrence of any of 6 whitespaces <sup>2</sup></td>
+  </tr>
+  <tr>
+    <td align="center">
+      <code>strcspn</code> <sup>1</sup><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.74</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.29</b> GB/s
+    </td>
+    <td align="center">
+      <code>.find_first_of</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.25</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.23</b> GB/s
+    </td>
+    <td align="center">
+      <code>re.finditer</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.06</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.02</b> GB/s
+    </td>
+    <td align="center">
+      <code>sz_find_charset</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.43</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.23</b> GB/s
+    </td>
+  </tr>
+  <!-- Characters, reverse order -->
+  <tr>
+    <td colspan="4" align="center">find the last occurrence of any of 6 whitespaces <sup>2</sup></td>
+  </tr>
+  <tr>
+    <td align="center">❌</td>
+    <td align="center">
+      <code>.find_last_of</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.25</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.25</b> GB/s
+    </td>
+    <td align="center">❌</td>
+    <td align="center">
+      <code>sz_rfind_charset</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>0.43</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>0.23</b> GB/s
+    </td>
+  </tr>
+  <!-- Random Generation -->
+  <tr>
+    <td colspan="4" align="center">Random string from a given alphabet, 20 bytes long <sup>5</sup></td>
+  </tr>
+  <tr>
+    <td align="center">
+      <code>rand() % n</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>18.0</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>9.4</b> MB/s
+    </td>
+    <td align="center">
+      <code>uniform_int_distribution</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>47.2</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>20.4</b> MB/s
+    </td>
+    <td align="center">
+      <code>join(random.choices(...))</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>13.3</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>5.9</b> MB/s
+    </td>
+    <td align="center">
+      <code>sz_generate</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>56.2</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>25.8</b> MB/s
+    </td>
+  </tr>
+  <!-- Sorting -->
+  <tr>
+    <td colspan="4" align="center">Get sorted order, ≅ 8 million English words <sup>6</sup></td>
+  </tr>
+  <tr>
+    <td align="center">
+      <code>qsort_r</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>3.55</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>5.77</b> s
+    </td>
+    <td align="center">
+      <code>std::sort</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>2.79</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>4.02</b> s
+    </td>
+    <td align="center">
+      <code>numpy.argsort</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>7.58</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>13.00</b> s
+    </td>
+    <td align="center">
+      <code>sz_sort</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>1.91</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>2.37</b> s
+    </td>
+  </tr>
+  <!-- Edit Distance -->
+  <tr>
+    <td colspan="4" align="center">Levenshtein edit distance, ≅ 5 bytes long</td>
+  </tr>
+  <tr>
+    <td align="center">❌</td>
+    <td align="center">❌</td>
+    <td align="center">
+      via <code>jellyfish</code> <sup>3</sup><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>1,550</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>2,220</b> ns
+    </td>
+    <td align="center">
+      <code>sz_edit_distance</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>99</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>180</b> ns
+    </td>
+  </tr>
+  <!-- Alignment Score -->
+  <tr>
+    <td colspan="4" align="center">Needleman-Wunsch alignment scores, ≅ 10 K aminoacids long</td>
+  </tr>
+  <tr>
+    <td align="center">❌</td>
+    <td align="center">❌</td>
+    <td align="center">
+      via <code>biopython</code> <sup>4</sup><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>257</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>367</b> ms
+    </td>
+    <td align="center">
+      <code>sz_alignment_score</code><br/>
+      <span style="color:#ABABAB;">x86:</span> <b>73</b> &centerdot;
+      <span style="color:#ABABAB;">arm:</span> <b>177</b> ms
+    </td>
+  </tr>
+</table>
+
+> Most benchmarks were conducted on a 1 GB English text corpus, with an average word length of 5 characters.
+> The code was compiled with GCC 12, using `glibc` v2.35.
+> The benchmarks performed on Arm-based Graviton3 AWS `c7g` instances and `r7iz` Intel Sapphire Rapids.
+> Most modern Arm-based 64-bit CPUs will have similar relative speedups.
+> Variance withing x86 CPUs will be larger.
+> <sup>1</sup> Unlike other libraries, LibC requires strings to be NULL-terminated.
+> <sup>2</sup> Six whitespaces in the ASCII set are: ` \t\n\v\f\r`. Python's and other standard libraries have specialized functions for those.
+> <sup>3</sup> Most Python libraries for strings are also implemented in C.
+> <sup>4</sup> Unlike the rest of BioPython, the alignment score computation is [implemented in C](https://github.com/biopython/biopython/blob/master/Bio/Align/_pairwisealigner.c).
+> <sup>5</sup> All modulo operations were conducted with `uint8_t` to allow compilers more optimization opportunities.
+> The C++ STL and StringZilla benchmarks used a 64-bit [Mersenne Twister][faq-mersenne-twister] as the generator.
+> For C, C++, and StringZilla, an in-place update of the string was used.
+> In Python every string had to be allocated as a new object, which makes it less fair.
+> <sup>6</sup> Contrary to the popular opinion, Python's default `sorted` function works faster than the C and C++ standard libraries.
+> That holds for large lists or tuples of strings, but fails as soon as you need more complex logic, like sorting dictionaries by a string key, or producing the "sorted order" permutation.
+> The latter is very common in database engines and is most similar to `numpy.argsort`.
+> Despite being faster than the standard libraries, current StringZilla solution can be at least 4x faster without loss of generality.
+
+[faq-mersenne-twister]: https://en.wikipedia.org/wiki/Mersenne_Twister
+
+## Supported Functionality
+
+| Functionality                  | Maturity | C 99 | C++ 11 | Python | Swift | Rust |
+| :----------------------------- | :------- | :--- | :----- | :----- | :---- | :--- |
+| Substring Search               | 🌳        | ✅    | ✅      | ✅      | ✅     | ✅    |
+| Character Set Search           | 🌳        | ✅    | ✅      | ✅      | ✅     | ✅    |
+| Edit Distance                  | 🧐        | ✅    | ✅      | ✅      | ✅     | ❌    |
+| Small String Class             | 🧐        | ✅    | ✅      | ❌      | ❌     | ❌    |
+| Sorting & Sequence Operations  | 🚧        | ✅    | ✅      | ✅      | ❌     | ❌    |
+| Lazy Ranges, Compressed Arrays | 🧐        | ❌    | ✅      | ✅      | ❌     | ❌    |
+| Hashes & Fingerprints          | 🚧        | ✅    | ✅      | ❌      | ❌     | ❌    |
+
+> [!NOTE]
+> Current StringZilla design assumes little-endian architecture, ASCII or UTF-8 encoding, and 64-bit address space.
+> This covers most modern CPUs, including x86, Arm, RISC-V.
+> Feel free to open an issue if you need support for other architectures.
+
+> 🌳 parts are used in production.
+> 🧐 parts are in beta.
+> 🚧 parts are under active development, and are likely to break in subsequent releases.
 
 ## Quick Start: Python 🐍
 
@@ -49,7 +283,8 @@ Coming soon.
 
 ### Basic Usage
 
-StringZilla offers two mostly interchangeable core classes:
+If you've ever used the Python `str` or `bytes` class, you'll know what to expect.
+StringZilla's `Str` class is a hybrid of those two, providing `str`-like interface to byte-arrays.
 
 ```python
 from stringzilla import Str, File
@@ -58,8 +293,7 @@ text_from_str = Str('some-string')
 text_from_file = Str(File('some-file.txt'))
 ```
 
-The `Str` is designed to replace long Python `str` strings and wrap our C-level API.
-On the other hand, the `File` memory-maps a file from persistent memory without loading its copy into RAM.
+The `File` class memory-maps a file from persistent memory without loading its copy into RAM.
 The contents of that file would remain immutable, and the mapping can be shared by multiple Python processes simultaneously.
 A standard dataset pre-processing use case would be to map a sizeable textual dataset like Common Crawl into memory, spawn child processes, and split the job between them.
 
@@ -85,11 +319,12 @@ A standard dataset pre-processing use case would be to map a sizeable textual da
 Once split into a `Strs` object, you can sort, shuffle, and reorganize the slices.
 
 ```python
-lines: Strs = text.split(separator='\n')
-lines.sort()
-lines.shuffle(seed=42)
+lines: Strs = text.split(separator='\n') # 4 bytes per line overhead for under 4 GB of text
+lines.sort() # explodes to 16 bytes per line overhead for any length text
+lines.shuffle(seed=42) # reproducing dataset shuffling with a seed
 ```
 
+Assuming superior search speed splitting should also work 3x faster than with native Python strings.
 Need copies?
 
 ```python
@@ -97,16 +332,17 @@ sorted_copy: Strs = lines.sorted()
 shuffled_copy: Strs = lines.shuffled(seed=42)
 ```
 
-Basic `list`-like operations are also supported:
+Those collections of `Strs` are designed to keep the memory consumption low.
+If all the chunks are located in consecutive memory regions, the memory overhead can be as low as 4 bytes per chunk.
+That's designed to handle very large datasets, like [RedPajama][redpajama].
+To address all 20 Billion annotated english documents in it, one will need only 160 GB of RAM instead of Terabytes.
 
-```python
-lines.append('Pythonic string')
-lines.extend(shuffled_copy)
-```
+[redpajama]: https://github.com/togethercomputer/RedPajama-Data
 
 ### Low-Level Python API
 
-The StringZilla CPython bindings implement vector-call conventions for faster calls.
+Aside from calling the methods on the `Str` and `Strs` classes, you can also call the global functions directly on `str` and `bytes` instances.
+Assuming StringZilla CPython bindings are implemented [without any intermediate tools like SWIG or PyBind](https://ashvardanian.com/posts/pybind11-cpython-tutorial/), the call latency should be similar to native classes.
 
 ```py
 import stringzilla as sz
@@ -114,121 +350,916 @@ import stringzilla as sz
 contains: bool = sz.contains("haystack", "needle", start=0, end=9223372036854775807)
 offset: int = sz.find("haystack", "needle", start=0, end=9223372036854775807)
 count: int = sz.count("haystack", "needle", start=0, end=9223372036854775807, allowoverlap=False)
-levenstein: int = sz.levenstein("needle", "nidl")
 ```
 
-## Quick Start: C 🛠️
+### Edit Distances
+
+```py
+edit_distance: int = sz.edit_distance("needle", "nidl")
+```
+
+Several Python libraries provide edit distance computation.
+Most of them are implemented in C, but are rarely as fast as StringZilla.
+Computing pairwise distances between words in an English text you may expect following results:
+
+- [EditDistance](https://github.com/roy-ht/editdistance): 28.7s
+- [JellyFish](https://github.com/jamesturk/jellyfish/): 26.8s
+- [Levenshtein](https://github.com/maxbachmann/Levenshtein): 8.6s
+- StringZilla: __4.2s__
 
-There is an ABI-stable C 99 interface, in case you have a database, an operating system, or a runtime you want to integrate with StringZilla.
+Moreover, you can pass custom substitution matrices to compute the Needleman-Wunsch alignment scores.
+That task is very common in bioinformatics and computational biology.
+It's natively supported in BioPython, and its BLOSUM matrices can be converted to StringZilla's format.
+Alternatively, you can construct an arbitrary 256 by 256 cost matrix using NumPy.
+Depending on arguments, the result may be equal to the negative Levenshtein distance.
+
+```py
+import numpy as np
+import stringzilla as sz
+
+costs = np.zeros((256, 256), dtype=np.int8)
+costs.fill(-1)
+np.fill_diagonal(costs, 0)
+
+assert sz.alignment_score("first", "second", substitution_matrix=costs, gap_score=-1) == -sz.edit_distance(a, b)
+```
+
+<details>
+  <summary><b>§ Example converting from BioPython to StringZilla.</b></summary>
+
+```py
+import numpy as np
+from Bio import Align
+from Bio.Align import substitution_matrices
+
+aligner = Align.PairwiseAligner()
+aligner.substitution_matrix = substitution_matrices.load("BLOSUM62")
+aligner.open_gap_score = 1
+aligner.extend_gap_score = 1
+
+# Convert the matrix to NumPy
+subs_packed = np.array(aligner.substitution_matrix).astype(np.int8)
+subs_reconstructed = np.zeros((256, 256), dtype=np.int8)
+
+# Initialize all banned characters to a the largest possible penalty
+subs_reconstructed.fill(127)
+for packed_row, packed_row_aminoacid in enumerate(aligner.substitution_matrix.alphabet):
+    for packed_column, packed_column_aminoacid in enumerate(aligner.substitution_matrix.alphabet):
+        reconstructed_row = ord(packed_row_aminoacid)
+        reconstructed_column = ord(packed_column_aminoacid)
+        subs_reconstructed[reconstructed_row, reconstructed_column] = subs_packed[packed_row, packed_column]
+
+# Let's pick two examples for of tri-peptides (made of 3 aminoacids)
+glutathione = "ECG" # Need to rebuild human tissue?
+thyrotropin_releasing_hormone = "QHP" # Or to regulate your metabolism?
+
+assert sz.alignment_score(
+    glutathione,
+    thyrotropin_releasing_hormone, 
+    substitution_matrix=subs_reconstructed, 
+    gap_score=1) == aligner.score(glutathione, thyrotropin_releasing_hormone) # Equal to 6
+```
+
+</details>
+
+## Quick Start: C/C++ 🛠️
+
+The C library is header-only, so you can just copy the `stringzilla.h` header into your project.
+Same applies to C++, where you would copy the `stringzilla.hpp` header.
+Alternatively, add it as a submodule, and include it in your build system.
+
+```sh
+git submodule add https://github.com/ashvardanian/stringzilla.git
+```
+
+Or using a pure CMake approach:
+
+```cmake
+FetchContent_Declare(stringzilla GIT_REPOSITORY https://github.com/ashvardanian/stringzilla.git)
+FetchContent_MakeAvailable(stringzilla)
+```
+
+Last, but not the least, you can also install it as a library, and link against it.
+This approach is worse for inlining, but brings dynamic runtime dispatch for the most advanced CPU features.
+
+### Basic Usage with C 99 and Newer
+
+There is a stable C 99 interface, where all function names are prefixed with `sz_`.
+Most interfaces are well documented, and come with self-explanatory names and examples.
+In some cases, hardware specific overloads are available, like `sz_find_avx512` or `sz_find_neon`.
+Both are companions of the `sz_find`, first for x86 CPUs with AVX-512 support, and second for Arm NEON-capable CPUs.
 
 ```c
-#include "stringzilla.h"
+#include <stringzilla/stringzilla.h>
 
 // Initialize your haystack and needle
 sz_string_view_t haystack = {your_text, your_text_length};
 sz_string_view_t needle = {your_subtext, your_subtext_length};
 
 // Perform string-level operations
-sz_size_t character_count = sz_count_char(haystack.start, haystack.length, "a");
-sz_size_t substring_position = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+sz_size_t substring_position = sz_find(haystack.start, haystack.length, needle.start, needle.length);
+sz_size_t substring_position = sz_find_avx512(haystack.start, haystack.length, needle.start, needle.length);
+sz_size_t substring_position = sz_find_neon(haystack.start, haystack.length, needle.start, needle.length);
 
 // Hash strings
-sz_u32_t crc32 = sz_hash_crc32(haystack.start, haystack.length);
+sz_u64_t hash = sz_hash(haystack.start, haystack.length);
 
 // Perform collection level operations
 sz_sequence_t array = {your_order, your_count, your_get_start, your_get_length, your_handle};
 sz_sort(&array, &your_config);
 ```
 
-## Contributing 👾
+<details>
+  <summary><b>§ Mapping from LibC to StringZilla.</b></summary>
+
+By design, StringZilla has a couple of notable differences from LibC:
+
+1. all strings are expected to have a length, and are not necessarily null-terminated.
+2. every operations has a reverse order counterpart.
+
+That way `sz_find` and `sz_rfind` are similar to `strstr` and `strrstr` in LibC.
+Similarly, `sz_find_byte` and `sz_rfind_byte` replace `memchr` and `memrchr`.
+The `sz_find_charset` maps to `strspn` and `strcspn`, while `sz_rfind_charset` has no sibling in LibC.
+
+<table>
+    <tr>
+        <th>LibC Functionality</th>
+        <th>StringZilla Equivalents</th>
+    </tr>
+    <tr>
+        <td><code>memchr(haystack, needle, haystack_length)</code>, <code>strchr</code></td>
+        <td><code>sz_find_byte(haystack, haystack_length, needle)</code></td>
+    </tr>
+    <tr>
+        <td><code>memrchr(haystack, needle, haystack_length)</code></td>
+        <td><code>sz_rfind_byte(haystack, haystack_length, needle)</code></td>
+    </tr>
+    <tr>
+        <td><code>memcmp</code>, <code>strcmp</code></td>
+        <td><code>sz_order</code>, <code>sz_equal</code></td>
+    </tr>
+    <tr>
+        <td><code>strlen(haystack)</code></td>
+        <td><code>sz_find_byte(haystack, haystack_length, needle)</code></td>
+    </tr>
+    <tr>
+        <td><code>strcspn(haystack, needles)</code></td>
+        <td><code>sz_rfind_charset(haystack, haystack_length, needles_bitset)</code></td>
+    </tr>
+    <tr>
+        <td><code>strspn(haystack, needles)</code></td>
+        <td><code>sz_find_charset(haystack, haystack_length, needles_bitset)</code></td>
+    </tr>
+    <tr>
+        <td><code>memmem(haystack, haystack_length, needle, needle_length)</code>, <code>strstr</code></td>
+        <td><code>sz_find(haystack, haystack_length, needle, needle_length)</code></td>
+    </tr>
+    <tr>
+        <td><code>memcpy(destination, source, destination_length)</code></td>
+        <td><code>sz_copy(destination, source, destination_length)</code></td>
+    </tr>
+    <tr>
+        <td><code>memmove(destination, source, destination_length)</code></td>
+        <td><code>sz_move(destination, source, destination_length)</code></td>
+    </tr>
+    <tr>
+        <td><code>memset(destination, value, destination_length)</code></td>
+        <td><code>sz_fill(destination, destination_length, value)</code></td>
+    </tr>
+</table>
+
+</details>
+
+### Basic Usage with C++ 11 and Newer
+
+There is a stable C++ 11 interface available in the `ashvardanian::stringzilla` namespace.
+It comes with two STL-like classes: `string_view` and `string`.
+The first is a non-owning view of a string, and the second is a mutable string with a [Small String Optimization][faq-sso].
+
+```cpp
+#include <stringzilla/stringzilla.hpp>
+
+namespace sz = ashvardanian::stringzilla;
+
+sz::string haystack = "some string";
+sz::string_view needle = sz::string_view(haystack).substr(0, 4);
+
+auto substring_position = haystack.find(needle); // Or `rfind`
+auto hash = std::hash<sz::string_view>(haystack); // Compatible with STL's `std::hash`
+
+haystack.end() - haystack.begin() == haystack.size(); // Or `rbegin`, `rend`
+haystack.find_first_of(" \w\t") == 4; // Or `find_last_of`, `find_first_not_of`, `find_last_not_of`
+haystack.starts_with(needle) == true; // Or `ends_with`
+haystack.remove_prefix(needle.size()); // Why is this operation in-place?!
+haystack.contains(needle) == true; // STL has this only from C++ 23 onwards
+haystack.compare(needle) == 1; // Or `haystack <=> needle` in C++ 20 and beyond
+```
+
+StringZilla also provides string literals for automatic type resolution, [similar to STL][stl-literal]:
 
-Future development plans include:
+```cpp
+using sz::literals::operator""_sz;
+using std::literals::operator""sv;
 
-- [x] [Replace PyBind11 with CPython](https://github.com/ashvardanian/StringZilla/issues/35), [blog](https://ashvardanian.com/posts/pybind11-cpython-tutorial/)
-- [x] [Bindings for JavaScript](https://github.com/ashvardanian/StringZilla/issues/25)
-- [ ] [Faster string sorting algorithm](https://github.com/ashvardanian/StringZilla/issues/45)
-- [ ] [Reverse-order operations in Python](https://github.com/ashvardanian/StringZilla/issues/12)
-- [ ] [Splitting with multiple separators at once](https://github.com/ashvardanian/StringZilla/issues/29)
-- [ ] Splitting CSV rows into columns
-- [ ] UTF-8 validation.
-- [ ] Arm SVE backend
-- [ ] Bindings for Java and Rust
+auto a = "some string"; // char const *
+auto b = "some string"sv; // std::string_view
+auto b = "some string"_sz; // sz::string_view
+```
 
-Here's how to set up your dev environment and run some tests.
+[stl-literal]: https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv
 
-### Development
+### Memory Ownership and Small String Optimization
 
-CPython:
+Most operations in StringZilla don't assume any memory ownership.
+But in addition to the read-only search-like operations StringZilla provides a minimalistic C and C++ implementations for a memory owning string "class".
+Like other efficient string implementations, it uses the [Small String Optimization][faq-sso] (SSO) to avoid heap allocations for short strings.
 
-```sh
-# Clean up, install, and test!
-rm -rf build && pip install -e . && pytest scripts/ -s -x
+[faq-sso]: https://cpp-optimizations.netlify.app/small_strings/
 
-# Install without dependencies
-pip install -e . --no-index --no-deps
+```c
+typedef union sz_string_t {
+    struct internal {
+        sz_ptr_t start;
+        sz_u8_t length;
+        char chars[SZ_STRING_INTERNAL_SPACE]; /// Ends with a null-terminator.
+    } internal;
+
+    struct external {
+        sz_ptr_t start;
+        sz_size_t length;        
+        sz_size_t space; /// The length of the heap-allocated buffer.
+        sz_size_t padding;
+    } external;
+
+} sz_string_t;
 ```
 
-NodeJS:
+As one can see, a short string can be kept on the stack, if it fits within `internal.chars` array.
+Before 2015 GCC string implementation was just 8 bytes, and could only fit 7 characters.
+Different STL implementations today have different thresholds for the Small String Optimization.
+Similar to GCC, StringZilla is 32 bytes in size, and similar to Clang it can fit 22 characters on stack.
+Our layout might be preferential, if you want to avoid branches.
+If you use a different compiler, you may want to check it's SSO buffer size with a [simple Gist](https://gist.github.com/ashvardanian/c197f15732d9855c4e070797adf17b21).
 
-```sh
-npm install && npm test
+|                       | `libstdc++` in  GCC 13 | `libc++` in Clang 17 | StringZilla |
+| :-------------------- | ---------------------: | -------------------: | ----------: |
+| `sizeof(std::string)` |                     32 |                   24 |          32 |
+| Small String Capacity |                     15 |               __22__ |      __22__ |
+
+This design has been since ported to many high-level programming languages.
+Swift, for example, [can store 15 bytes](https://developer.apple.com/documentation/swift/substring/withutf8(_:)#discussion) in the `String` instance itself.
+StringZilla implements SSO at the C level, providing the `sz_string_t` union and a simple API for primary operations.
+
+```c
+sz_memory_allocator_t allocator;
+sz_string_t string;
+
+// Init and make sure we are on stack
+sz_string_init(&string);
+sz_string_is_on_stack(&string); // == sz_true_k
+
+// Optionally pre-allocate space on the heap for future insertions.
+sz_string_grow(&string, 100, &allocator); // == sz_true_k
+
+// Append, erase, insert into the string.
+sz_string_append(&string, "_Hello_", 7, &allocator); // == sz_true_k
+sz_string_append(&string, "world", 5, &allocator); // == sz_true_k
+sz_string_erase(&string, 0, 1);
+
+// Unpacking & introspection.
+sz_ptr_t string_start;
+sz_size_t string_length;
+sz_size_t string_space;
+sz_bool_t string_is_external;
+sz_string_unpack(string, &string_start, &string_length, &string_space, &string_is_external);
+sz_equal(string_start, "Hello_world", 11); // == sz_true_k
+
+// Reclaim some memory.
+sz_string_shrink_to_fit(&string, &allocator); // == sz_true_k
+sz_string_free(&string, &allocator);
+```
+
+Unlike the conventional C strings, the `sz_string_t` is allowed to contain null characters.
+To safely print those, pass the `string_length` to `printf` as well.
+
+```c
+printf("%.*s\n", (int)string_length, string_start);
 ```
 
-### Benchmarking
+### What's Wrong with the C++ Standard Library?
+
+| C++ Code                             | Evaluation Result | Invoked Signature              |
+| :----------------------------------- | :---------------- | :----------------------------- |
+| `"Loose"s.replace(2, 2, "vath"s, 1)` | `"Loathe"` 🤢      | `(pos1, count1, str2, pos2)`   |
+| `"Loose"s.replace(2, 2, "vath", 1)`  | `"Love"` 🥰        | `(pos1, count1, str2, count2)` |
+
+StringZilla is designed to be a drop-in replacement for the C++ Standard Templates Library.
+That said, some of the design decisions of STL strings are highly controversial, error-prone, and expensive.
+Most notably:
+
+1. Argument order for `replace`, `insert`, `erase` and similar functions is impossible to guess.
+2. Bounds-checking exceptions for `substr`-like functions are only thrown for one side of the range.
+3. Returning string copies in `substr`-like functions results in absurd volume of allocations.
+4. Incremental construction via `push_back`-like functions goes through too many branches.
+5. Inconsistency between `string` and `string_view` methods, like the lack of `remove_prefix` and `remove_suffix`.
+
+Check the following set of asserts validating the `std::string` specification.
+It's not realistic to expect the average developer to remember the [14 overloads of `std::string::replace`][stl-replace].
+
+[stl-replace]: https://en.cppreference.com/w/cpp/string/basic_string/replace
+
+```cpp
+using str = std::string;
+
+assert(str("hello world").substr(6) == "world");
+assert(str("hello world").substr(6, 100) == "world"); // 106 is beyond the length of the string, but its OK
+assert_throws(str("hello world").substr(100), std::out_of_range);   // 100 is beyond the length of the string
+assert_throws(str("hello world").substr(20, 5), std::out_of_range); // 20 is beyond the length of the string
+assert_throws(str("hello world").substr(-1, 5), std::out_of_range); // -1 casts to unsigned without any warnings...
+assert(str("hello world").substr(0, -1) == "hello world");          // -1 casts to unsigned without any warnings...
+
+assert(str("hello").replace(1, 2, "123") == "h123lo");
+assert(str("hello").replace(1, 2, str("123"), 1) == "h23lo");
+assert(str("hello").replace(1, 2, "123", 1) == "h1lo");
+assert(str("hello").replace(1, 2, "123", 1, 1) == "h2lo");
+assert(str("hello").replace(1, 2, str("123"), 1, 1) == "h2lo");
+assert(str("hello").replace(1, 2, 3, 'a') == "haaalo");
+assert(str("hello").replace(1, 2, {'a', 'b'}) == "hablo");
+```
 
-To benchmark on some custom file and pattern combinations:
+To avoid those issues, StringZilla provides an alternative consistent interface.
+It supports signed arguments, and doesn't have more than 3 arguments per function or
+The standard API and our alternative can be conditionally disabled with `SZ_SAFETY_OVER_COMPATIBILITY=1`.
+When it's enabled, the _~~subjectively~~_ risky overloads from the Standard will be disabled.
 
-```sh
-python scripts/bench.py --haystack_path "your file" --needle "your pattern"
+```cpp
+using str = sz::string;
+
+str("a:b").front(1) == "a"; // no checks, unlike `substr`
+str("a:b").back(-1) == "b"; // accepting negative indices
+str("a:b").sub(1, -1) == ":"; // similar to Python's `"a:b"[1:-1]`
+str("a:b").sub(-2, -1) == ":"; // similar to Python's `"a:b"[-2:-1]`
+str("a:b").sub(-2, 1) == ""; // similar to Python's `"a:b"[-2:1]`
+"a:b"_sz[{-2, -1}] == ":"; // works on views and overloads `operator[]`
 ```
 
-To benchmark on synthetic data:
+Assuming StringZilla is a header-only library you can use the full API in some translation units and gradually transition to safer restricted API in others.
+Bonus - all the bound checking is branchless, so it has a constant cost and won't hurt your branch predictor.
+
+### Beyond the C++ Standard Library - Learning from Python
+
+Python is arguably the most popular programming language for data science.
+In part, that's due to the simplicity of its standard interfaces.
+StringZilla brings some of that functionality to C++.
+
+- Content checks: `isalnum`, `isalpha`, `isascii`, `isdigit`, `islower`, `isspace`, `isupper`.
+- Trimming character sets: `lstrip`, `rstrip`, `strip`.
+- Trimming string matches: `remove_prefix`, `remove_suffix`.
+- Ranges of search results: `splitlines`, `split`, `rsplit`.
+- Number of non-overlapping substring matches: `count`.
+- Partitioning: `partition`, `rpartition`.
+
+For example, when parsing documents, it is often useful to split it into substrings.
+Most often, after that, you would compute the length of the skipped part, the offset and the length of the remaining part.
+This results in a lot of pointer arithmetic and is error-prone.
+StringZilla provides a convenient `partition` function, which returns a tuple of three string views, making the code cleaner.
+
+```cpp
+auto parts = haystack.partition(':'); // Matching a character
+auto [before, match, after] = haystack.partition(':'); // Structure unpacking
+auto [before, match, after] = haystack.partition(char_set(":;")); // Character-set argument
+auto [before, match, after] = haystack.partition(" : "); // String argument
+auto [before, match, after] = haystack.rpartition(sz::whitespaces); // Split around the last whitespace
+```
 
-```sh
-python scripts/bench.py --haystack_pattern "abcd" --haystack_length 1e9 --needle "abce"
+Combining those with the `split` function, one can easily parse a CSV file or HTTP headers.
+
+```cpp
+for (auto line : haystack.split("\r\n")) {
+    auto [key, _, value] = line.partition(':');
+    headers[key.strip()] = value.strip();
+}
 ```
 
-### Packaging
+Some other extensions are not present in the Python standard library either.
+Let's go through the C++ functionality category by category.
 
-To validate packaging:
+- [Splits and Ranges](#splits-and-ranges).
+- [Concatenating Strings without Allocations](#concatenating-strings-without-allocations).
+- [Random Generation](#random-generation).
+- [Edit Distances and Fuzzy Search](#levenshtein-edit-distance-and-alignment-scores).
 
-```sh
-cibuildwheel --platform linux
+Some of the StringZilla interfaces are not available even Python's native `str` class.
+Here is a sneak peek of the most useful ones.
+
+```cpp
+text.hash(); // -> 64 bit unsigned integer 
+text.ssize(); // -> 64 bit signed length to avoid `static_cast<std::ssize_t>(text.size())`
+text.contains_only(" \w\t"); // == text.find_first_not_of(char_set(" \w\t")) == npos;
+text.contains(sz::whitespaces); // == text.find(char_set(sz::whitespaces)) != npos;
+
+// Simpler slicing than `substr`
+text.front(10); // -> sz::string_view
+text.back(10); // -> sz::string_view
+
+// Safe variants, which clamp the range into the string bounds
+using sz::string::cap;
+text.front(10, cap) == text.front(std::min(10, text.size()));
+text.back(10, cap) == text.back(std::min(10, text.size()));
+
+// Character set filtering
+text.lstrip(sz::whitespaces).rstrip(sz::newlines); // like Python
+text.front(sz::whitespaces); // all leading whitespaces
+text.back(sz::digits); // all numerical symbols forming the suffix
+
+// Incremental construction
+using sz::string::unchecked;
+text.push_back('x'); // no surprises here
+text.push_back('x', unchecked); // no bounds checking, Rust style
+text.try_push_back('x'); // returns `false` if the string is full and the allocation failed
+
+sz::concatenate(text, "@", domain, ".", tld); // No allocations
 ```
 
-### Compiling C++ Tests
+### Splits and Ranges
 
-```sh
-cmake -B ./build_release -DSTRINGZILLA_BUILD_TEST=1 && make -C ./build_release -j && ./build_release/stringzilla_test
+One of the most common use cases is to split a string into a collection of substrings.
+Which would often result in [StackOverflow lookups][so-split] and snippets like the one below.
+
+[so-split]: https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
+
+```cpp
+std::vector<std::string> lines = split(haystack, "\r\n"); // string delimiter
+std::vector<std::string> words = split(lines, ' '); // character delimiter
 ```
 
-On MacOS it's recommended to use non-default toolchain:
+Those allocate memory for each string and the temporary vectors.
+Each allocation can be orders of magnitude more expensive, than even serial `for`-loop over characters.
+To avoid those, StringZilla provides lazily-evaluated ranges, compatible with the [Range-v3][range-v3] library.
 
-```sh
-# Install dependencies
-brew install libomp llvm
+[range-v3]: https://github.com/ericniebler/range-v3
 
-# Compile and run tests
-cmake -B ./build_release \
-    -DCMAKE_C_COMPILER="/opt/homebrew/opt/llvm/bin/clang" \
-    -DCMAKE_CXX_COMPILER="/opt/homebrew/opt/llvm/bin/clang++" \
-    -DSTRINGZILLA_USE_OPENMP=1 \
-    -DSTRINGZILLA_BUILD_TEST=1 \
-    && \
-    make -C ./build_release -j && ./build_release/stringzilla_test
+```cpp
+for (auto line : haystack.split("\r\n"))
+    for (auto word : line.split(char_set(" \w\t.,;:!?")))
+        std::cout << word << std::endl;
 ```
 
-## License 📜
+Each of those is available in reverse order as well.
+It also allows interleaving matches, if you want both inclusions of `xx` in `xxx`.
+Debugging pointer offsets is not a pleasant exercise, so keep the following functions in mind.
 
-Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.
+- `haystack.[r]find_all(needle, interleaving)`
+- `haystack.[r]find_all(char_set(""))`
+- `haystack.[r]split(needle)`
+- `haystack.[r]split(char_set(""))`
+
+For $N$ matches the split functions will report $N+1$ matches, potentially including empty strings.
+Ranges have a few convenience methods as well:
+
+```cpp
+range.size(); // -> std::size_t
+range.empty(); // -> bool
+range.template to<std::set<std::sting>>(); 
+range.template to<std::vector<std::sting_view>>(); 
+```
+
+### Concatenating Strings without Allocations
+
+Another common string operation is concatenation.
+The STL provides `std::string::operator+` and `std::string::append`, but those are not very efficient, if multiple invocations are performed.
+
+```cpp
+std::string name, domain, tld;
+auto email = name + "@" + domain + "." + tld; // 4 allocations
+```
+
+The efficient approach would be to pre-allocate the memory and copy the strings into it.
+
+```cpp
+std::string email;
+email.reserve(name.size() + domain.size() + tld.size() + 2);
+email.append(name), email.append("@"), email.append(domain), email.append("."), email.append(tld);
+```
+
+That's mouthful and error-prone.
+StringZilla provides a more convenient `concatenate` function, which takes a variadic number of arguments.
+It also overrides the `operator|` to concatenate strings lazily, without any allocations.
+
+```cpp
+auto email = sz::concatenate(name, "@", domain, ".", tld);   // 0 allocations
+auto email = name | "@" | domain | "." | tld;                // 0 allocations
+sz::string email = name | "@" | domain | "." | tld;          // 1 allocations
+```
+
+### Random Generation
+
+Software developers often need to generate random strings for testing purposes.
+The STL provides `std::generate` and `std::random_device`, that can be used with StringZilla.
+
+```cpp
+sz::string random_string(std::size_t length, char const *alphabet, std::size_t cardinality) {
+    sz::string result(length, '\0');
+    static std::random_device seed_source; // Too expensive to construct every time
+    std::mt19937 generator(seed_source());
+    std::uniform_int_distribution<std::size_t> distribution(1, cardinality);
+    std::generate(result.begin(), result.end(), [&]() { return alphabet[distribution(generator)]; });
+    return result;
+}
+```
+
+Mouthful and slow.
+StringZilla provides a C native method - `sz_generate` and a convenient C++ wrapper - `sz::generate`.
+Similar to Python it also defines the commonly used character sets.
+
+```cpp
+auto protein = sz::string::random(300, "ARNDCQEGHILKMFPSTWYV"); // static method
+auto dna = sz::basic_string<custom_allocator>::random(3_000_000_000, "ACGT");
+
+dna.randomize("ACGT"); // `noexcept` pre-allocated version
+dna.randomize(&std::rand, "ACGT"); // pass any generator, like `std::mt19937`
+
+char uuid[36];
+sz::randomize(sz::string_span(uuid, 36), "0123456789abcdef-"); // Overwrite any buffer
+```
+
+### Levenshtein Edit Distance and Alignment Scores
+
+```cpp
+sz::edit_distance(first, second[, upper_bound[, allocator]]) -> std::size_t;
+
+std::int8_t costs[256][256]; // Substitution costs matrix
+sz::alignment_score(first, second, costs[, gap_score[, allocator]) -> std::ptrdiff_t;
+```
+
+### Sorting in C and C++
+
+LibC provides `qsort` and STL provides `std::sort`.
+Both have their quarks.
+The LibC standard has no way to pass a context to the comparison function, that's only possible with platform-specific extensions.
+Those have [different arguments order](https://stackoverflow.com/a/39561369) on every OS.
+
+```c
+// Linux: https://linux.die.net/man/3/qsort_r
+void qsort_r(void *elements, size_t count, size_t element_width, 
+    int (*compare)(void const *left, void const *right, void *context),
+    void *context);
+// MacOS and FreeBSD: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/qsort_r.3.html
+void qsort_r(void *elements, size_t count, size_t element_width, 
+    void *context,
+    int (*compare)(void *context, void const *left, void const *right));
+// Windows conflicts with ISO `qsort_s`: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/qsort-s?view=msvc-170
+void qsort_s(id *elements, size_t count, size_t element_width, 
+    int (*compare)(void *context, void const *left, void const *right),
+    void *context);
+```
+
+C++ generic algorithm is not perfect either.
+There is no guarantee in the standard that `std::sort` won't allocate any memory.
+If you are running on embedded, in real-time or on 100+ CPU cores per node, you may want to avoid that.
+StringZilla doesn't solve the general case, but hopes to improve the performance for strings.
+Use `sz_sort`, or the high-level `sz::sorted_order`, which can be used sort any collection of elements convertible to `sz::string_view`.
+
+```cpp
+std::vector<std::string> data({"c", "b", "a"});
+std::vector<std::size_t> order = sz::sorted_order(data); //< Simple shortcut
+
+// Or, taking care of memory allocation:
+sz::sorted_order(data.begin(), data.end(), order.data(), [](auto const &x) -> sz::string_view { return x; });
+```
+
+### Standard C++ Containers with String Keys
+
+The C++ Standard Templates Library provides several associative containers, often used with string keys.
+
+```cpp
+std::map<std::string, int, std::less<std::string>> sorted_words;
+std::unordered_map<std::string, int, std::hash<std::string>, std::equal_to<std::string>> words;
+```
+
+The performance of those containers is often limited by the performance of the string keys, especially on reads.
+StringZilla can be used to accelerate containers with `std::string` keys, by overriding the default comparator and hash functions.
+
+```cpp
+std::map<std::string, int, sz::string_view_less> sorted_words;
+std::unordered_map<std::string, int, sz::string_view_hash, sz::string_view_equal_to> words;
+```
+
+Alternatively, a better approach would be to use the `sz::string` class as a key.
+The right hash function and comparator would be automatically selected and the performance gains would be more noticeable if the keys are short.
+
+```cpp
+std::map<sz::string, int> sorted_words;
+std::unordered_map<sz::string, int> words;
+```
+
+### Compilation Settings and Debugging
+
+__`SZ_DEBUG`__:
+
+> For maximal performance, the C library does not perform any bounds checking in Release builds.
+> In C++, bounds checking happens only in places where the STL `std::string` would do it.
+> If you want to enable more aggressive bounds-checking, define `SZ_DEBUG` before including the header.
+> If not explicitly set, it will be inferred from the build type.
+
+__`SZ_USE_X86_AVX512`, `SZ_USE_X86_AVX2`, `SZ_USE_ARM_NEON`__:
+
+> One can explicitly disable certain families of SIMD instructions for compatibility purposes.
+> Default values are inferred at compile time.
+
+__`SZ_DYNAMIC_DISPATCH`__:
+
+> By default, StringZilla is a header-only library.
+> But if you are running on different generations of devices, it makes sense to pre-compile the library for all supported generations at once, and dispatch at runtime.
+> This flag does just that and is used to produce the `stringzilla.so` shared library, as well as the Python bindings.
+
+__`SZ_USE_MISALIGNED_LOADS`__:
+
+> By default, StringZilla avoids misaligned loads.
+> If supported, it replaces many byte-level operations with word-level ones.
+> Going from `char`-like types to `uint64_t`-like ones can significantly accelerate the serial (SWAR) backend.
+> So consider enabling it if you are building for some embedded device.
+
+__`SZ_AVOID_LIBC`__:
+
+> When using the C header-only library one can disable the use of LibC.
+> This may affect the type resolution system on obscure hardware platforms. 
+
+__`SZ_AVOID_STL`__:
+
+> When using the C++ interface one can disable conversions from `std::string` to `sz::string` and back.
+> If not needed, the `<string>` and `<string_view>` headers will be excluded, reducing compilation time.
+
+__`STRINGZILLA_BUILD_SHARED`, `STRINGZILLA_BUILD_TEST`, `STRINGZILLA_BUILD_BENCHMARK`, `STRINGZILLA_TARGET_ARCH`__ for CMake users:
+
+> When compiling the tests and benchmarks, you can explicitly set the target hardware architecture.
+> It's synonymous to GCC's `-march` flag and is used to enable/disable the appropriate instruction sets.
+> You can also disable the shared library build, if you don't need it.
+
+## Quick Start: Rust 🦀
+
+StringZilla is available as a Rust crate.
+It currently covers only the most basic functionality, but is planned to be extended to cover the full C++ API.
+
+```rust
+let my_string: String = String::from("Hello, world!");
+let my_str = my_string.as_str();
+let my_cow_str = Cow::from(&my_string);
+
+// Use the generic function with a String
+assert_eq!(my_string.sz_find("world"), Some(7));
+assert_eq!(my_string.sz_rfind("world"), Some(7));
+assert_eq!(my_string.sz_find_char_from("world"), Some(2));
+assert_eq!(my_string.sz_rfind_char_from("world"), Some(11));
+assert_eq!(my_string.sz_find_char_not_from("world"), Some(0));
+assert_eq!(my_string.sz_rfind_char_not_from("world"), Some(12));
+
+// Same works for &str and Cow<'_, str>
+assert_eq!(my_str.sz_find("world"), Some(7));
+assert_eq!(my_cow_str.as_ref().sz_find("world"), Some(7));
+```
+
+## Quick Start: Swift 🍏
+
+StringZilla is available as a Swift package.
+It currently covers only the most basic functionality, but is planned to be extended to cover the full C++ API.
+
+```swift
+var s = "Hello, world! Welcome to StringZilla. 👋"
+s[s.findFirst(substring: "world")!...] // "world! Welcome to StringZilla. 👋")    
+s[s.findLast(substring: "o")!...] // "o StringZilla. 👋")
+s[s.findFirst(characterFrom: "aeiou")!...] // "ello, world! Welcome to StringZilla. 👋")
+s[s.findLast(characterFrom: "aeiou")!...] // "a. 👋")
+s[s.findFirst(characterNotFrom: "aeiou")!...] // "Hello, world! Welcome to StringZilla. 👋"
+s.editDistance(from: "Hello, world!")! // 29
+```
+
+## Algorithms & Design Decisions 📚
+
+StringZilla aims to optimize some of the slowest string operations.
+Some popular operations, however, like equality comparisons and relative order checking, almost always complete on some of the very first bytes in either string.
+In such operations vectorization is almost useless, unless huge and very similar strings are considered.
+StringZilla implements those operations as well, but won't result in substantial speedups.
+
+### Exact Substring Search
+
+Substring search algorithms are generally divided into: comparison-based, automaton-based, and bit-parallel.
+Different families are effective for different alphabet sizes and needle lengths.
+The more operations are needed per-character - the more effective SIMD would be.
+The longer the needle - the more effective the skip-tables are.
+StringZilla uses different exact substring search algorithms for different needle lengths and backends:
+
+- When no SIMD is available - SWAR (SIMD Within A Register) algorithms are used on 64-bit words.
+- Boyer-Moore-Horspool (BMH) algorithm with Raita heuristic variation for longer needles.
+- SIMD algorithms are randomized to look at different parts of the needle.
+
+On very short needles, especially 1-4 characters long, brute force with SIMD is the fastest solution.
+On mid-length needles, bit-parallel algorithms are effective, as the character masks fit into 32-bit or 64-bit words.
+Either way, if the needle is under 64-bytes long, on haystack traversal we will still fetch every CPU cache line.
+So the only way to improve performance is to reduce the number of comparisons.
+The snippet below shows how StringZilla accomplishes that for needles of length two.
+
+https://github.com/ashvardanian/StringZilla/blob/266c01710dddf71fc44800f36c2f992ca9735f87/include/stringzilla/stringzilla.h#L1585-L1637
+
+Going beyond that, to long needles, Boyer-Moore (BM) and its variants are often the best choice.
+It has two tables: the good-suffix shift and the bad-character shift.
+Common choice is to use the simplified BMH algorithm, which only uses the bad-character shift table, reducing the pre-processing time.
+We do the same for mid-length needles up to 256 bytes long.
+That way the stack-allocated shift table remains small.
+
+https://github.com/ashvardanian/StringZilla/blob/46e957cd4f9ecd4945318dd3c48783dd11323f37/include/stringzilla/stringzilla.h#L1774-L1825
+
+In the C++ Standards Library, the `std::string::find` function uses the BMH algorithm with Raita's heuristic.
+Before comparing the entire string, it matches the first, last, and the middle character.
+Very practical, but can be slow for repetitive characters.
+Both SWAR and SIMD backends of StringZilla have a cheap pre-processing step, where we locate unique characters.
+This makes the library a lot more practical when dealing with non-English corpora.
+
+https://github.com/ashvardanian/StringZilla/blob/46e957cd4f9ecd4945318dd3c48783dd11323f37/include/stringzilla/stringzilla.h#L1398-L1431
 
----
+All those, still, have $O(hn)$ worst case complexity.
+To guarantee $O(h)$ worst case time complexity, the Apostolico-Giancarlo (AG) algorithm adds an additional skip-table.
+Preprocessing phase is $O(n+sigma)$ in time and space.
+On traversal, performs from $(h/n)$ to $(3h/2)$ comparisons.
+It however, isn't practical on modern CPUs.
+A simpler idea, the Galil-rule might be a more relevant optimizations, if many matches must be found.
 
-If you like this project, you may also enjoy [USearch][usearch], [UCall][ucall], [UForm][uform], [UStore][ustore], [SimSIMD][simsimd], and [TenPack][tenpack] 🤗
+Other algorithms previously considered and deprecated:
+
+- Apostolico-Giancarlo algorithm for longer needles. _Control-flow is too complex for efficient vectorization._
+- Shift-Or-based Bitap algorithm for short needles. _Slower than SWAR._
+- Horspool-style bad-character check in SIMD backends. _Effective only for very long needles, and very uneven character distributions between the needle and the haystack. Faster "character-in-set" check needed to generalize._
+
+> § Reading materials.
+> [Exact String Matching Algorithms in Java](https://www-igm.univ-mlv.fr/~lecroq/string).
+> [SIMD-friendly algorithms for substring searching](http://0x80.pl/articles/simd-strfind.html).
+
+### Levenshtein Edit Distance
+
+Levenshtein distance is the best known edit-distance for strings, that checks, how many insertions, deletions, and substitutions are needed to transform one string to another.
+It's extensively used in approximate string-matching, spell-checking, and bioinformatics.
+
+The computational cost of the Levenshtein distance is $O(n * m)$, where $n$ and $m$ are the lengths of the string arguments.
+To compute that, the naive approach requires $O(n * m)$ space to store the "Levenshtein matrix", the bottom-right corner of which will contain the Levenshtein distance.
+The algorithm producing the matrix has been simultaneously studied/discovered by the Soviet mathematicians Vladimir Levenshtein in 1965, Taras Vintsyuk in 1968, and American computer scientists - Robert Wagner, David Sankoff, Michael J. Fischer in the following years.
+Several optimizations are known:
+
+1. __Space Optimization__: The matrix can be computed in $O(min(n,m))$ space, by only storing the last two rows of the matrix.
+2. __Divide and Conquer__: Hirschberg's algorithm can be applied to decompose the computation into subtasks.
+3. __Automata__: Levenshtein automata can be effective, if one of the strings doesn't change, and is a subject to many comparisons.
+4. __Shift-Or__: Bit-parallel algorithms transpose the matrix into a bit-matrix, and perform bitwise operations on it.
+
+The last approach is quite powerful and performant, and is used by the great [RapidFuzz][rapidfuzz] library.
+It's less known, than the others, derived from the Baeza-Yates-Gonnet algorithm, extended to bounded edit-distance search by Manber and Wu in 1990s, and further extended by Gene Myers in 1999 and Heikki Hyyro between 2002 and 2004.
+
+StringZilla introduces a different approach, extensively used in Unum's internal combinatorial optimization libraries.
+The approach doesn't change the number of trivial operations, but performs them in a different order, removing the data dependency, that occurs when computing the insertion costs.
+This results in much better vectorization for intra-core parallelism and potentially multi-core evaluation of a single request.
+
+Next design goals:
+
+- [ ] Generalize fast traversals to rectangular matrices.
+- [ ] Port x86 AVX-512 solution to Arm NEON.
+
+> § Reading materials.
+> [Faster Levenshtein Distances with a SIMD-friendly Traversal Order](https://ashvardanian.com/posts/levenshtein-diagonal).
+
+[rapidfuzz]: https://github.com/rapidfuzz/RapidFuzz
+
+### Needleman-Wunsch Alignment Score for Bioinformatics
+
+The field of bioinformatics studies various representations of biological structures.
+The "primary" representations are generally strings over sparse alphabets:
+
+- [DNA][faq-dna] sequences, where the alphabet is {A, C, G, T}, ranging from ~100 characters for short reads to 3 billion for the human genome.
+- [RNA][faq-rna] sequences, where the alphabet is {A, C, G, U}, ranging from ~50 characters for tRNA to thousands for mRNA.
+- [Proteins][faq-protein], where the alphabet is made of 22 amino acids, ranging from 2 characters for [dipeptide][faq-dipeptide] to 35,000 for [Titin][faq-titin], the longest protein.
+
+The shorter the representation, the more often researchers may want to use custom substitution matrices.
+Meaning that the cost of a substitution between two characters may not be the same for all pairs.
+
+StringZilla adapts the fairly efficient two-row Wagner-Fisher algorithm as a baseline serial implementation of the Needleman-Wunsch score.
+It supports arbitrary alphabets up to 256 characters, and can be used with either [BLOSUM][faq-blosum], [PAM][faq-pam], or other substitution matrices.
+It also uses SIMD for hardware acceleration of the substitution lookups.
+This however, does not __yet__ break the data-dependency for insertion costs, where 80% of the time is wasted.
+With that solved, the SIMD implementation will become 5x faster than the serial one.
+
+[faq-dna]: https://en.wikipedia.org/wiki/DNA
+[faq-rna]: https://en.wikipedia.org/wiki/RNA
+[faq-protein]: https://en.wikipedia.org/wiki/Protein
+[faq-blosum]: https://en.wikipedia.org/wiki/BLOSUM
+[faq-pam]: https://en.wikipedia.org/wiki/Point_accepted_mutation
+[faq-dipeptide]: https://en.wikipedia.org/wiki/Dipeptide
+[faq-titin]: https://en.wikipedia.org/wiki/Titin
+
+### Random Generation
+
+Generating random strings from different alphabets is a very common operation.
+StringZilla accepts an arbitrary [Pseudorandom Number Generator][faq-prng] to produce noise, and an array of characters to sample from.
+Sampling is optimized to avoid integer division, a costly operation on modern CPUs.
+For that a 768-byte long lookup table is used to perform 2 lookups, 1 multiplication, 2 shifts, and 2 accumulations.
+
+https://github.com/ashvardanian/StringZilla/blob/266c01710dddf71fc44800f36c2f992ca9735f87/include/stringzilla/stringzilla.h#L2490-L2533
+
+[faq-prng]: https://en.wikipedia.org/wiki/Pseudorandom_number_generator
+
+### Sorting
+
+For lexicographic sorting of strings, StringZilla uses a "hybrid-hybrid" approach with $O(n * log(n))$ and.
+
+1. Radix sort for first bytes exported into a continuous buffer for locality.
+2. IntroSort on partially ordered chunks to balance efficiency and worst-case performance.
+   1. IntroSort begins with a QuickSort.
+   2. If the recursion depth exceeds a certain threshold, it switches to a HeapSort.
+
+Next design goals:
+
+- [ ] Generalize to arrays with over 4 billion entries.
+- [ ] Algorithmic improvements may yield another 3x performance gain.
+- [ ] SIMD-acceleration for the Radix slice.
+
+### Hashing
+
+> [!WARNING]
+> Hash functions are not cryptographically safe and are currently under active development.
+> They may change in future __minor__ releases.
+
+Choosing the right hashing algorithm for your application can be crucial from both performance and security standpoint.
+In StringZilla a 64-bit rolling hash function is reused for both string hashes and substring hashes, Rabin-style fingerprints.
+Rolling hashes take the same amount of time to compute hashes with different window sizes, and are fast to update.
+Those are not however perfect hashes, and collisions are frequent.
+StringZilla attempts to use SIMD, but the performance is not __yet__ satisfactory.
+On Intel Sapphire Rapids, the following numbers can be expected for N-way parallel variants.
+
+- 4-way AVX2 throughput with 64-bit integer multiplication (no native support): 0.28 GB/s.
+- 4-way AVX2 throughput with 32-bit integer multiplication: 0.54 GB/s.
+- 4-way AVX-512DQ throughput with 64-bit integer multiplication: 0.46 GB/s.
+- 4-way AVX-512 throughput with 32-bit integer multiplication: 0.58 GB/s.
+- 8-way AVX-512 throughput with 32-bit integer multiplication: 0.11 GB/s.
+
+Next design goals:
+
+- [ ] Try gear-hash and other rolling approaches.
+
+#### Why not CRC32?
+
+Cyclic Redundancy Check 32 is one of the most commonly used hash functions in Computer Science.
+It has in-hardware support on both x86 and Arm, for both 8-bit, 16-bit, 32-bit, and 64-bit words.
+The `0x1EDC6F41` polynomial is used in iSCSI, Btrfs, ext4, and the `0x04C11DB7` in SATA, Ethernet, Zlib, PNG.
+In case of Arm more than one polynomial is supported.
+It is, however, somewhat limiting for Big Data usecases, which often have to deal with more than 4 Billion strings, making collisions unavoidable.
+Moreover, the existing SIMD approaches are tricky, combining general purpose computations with specialized instructions, to utilize more silicon in every cycle.
+
+> § Reading materials.
+> [Comprehensive derivation of approaches](https://github.com/komrad36/CRC)
+> [Faster computation for 4 KB buffers on x86](https://www.corsix.org/content/fast-crc32c-4k)
+> [Comparing different lookup tables](https://create.stephan-brumme.com/crc32)
+> Great open-source implementations.
+> [By Peter Cawley](https://github.com/corsix/fast-crc32)
+> [By Stephan Brumme](https://github.com/stbrumme/crc32)
+
+#### Other Modern Alternatives
+
+[MurmurHash](https://github.com/aappleby/smhasher/blob/master/README.md) from 2008 by Austin Appleby is one of the best known non-cryptographic hashes.
+It has a very short implementation and is capable of producing 32-bit and 128-bit hashes.
+The [CityHash](https://opensource.googleblog.com/2011/04/introducing-cityhash) from 2011 by Google and the [xxHash](https://github.com/Cyan4973/xxHash) improve on that, better leveraging the super-scalar nature of modern CPUs and producing 64-bit and 128-bit hashes.
+
+Neither of those functions are cryptographic, unlike MD5, SHA, and BLAKE algorithms.
+Most of cryptographic hashes are based on the Merkle-Damgård construction, and aren't resistant to the length-extension attacks.
+Current state of the Art, might be the [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) algorithm.
+It's resistant to a broad range of attacks, can process 2 bytes per CPU cycle, and comes with a very optimized official implementation for C and Rust.
+It has the same 128-bit security level as the BLAKE2, and achieves its performance gains by reducing the number of mixing rounds, and processing data in 1 KiB chunks, which is great for longer strings, but may result in poor performance on short ones.
+
+All mentioned libraries have undergone extensive testing and are considered production-ready.
+They can definitely accelerate your application, but so may the downstream mixer.
+For instance, when a hash-table is constructed, the hashes are further shrunk to address table buckets.
+If the mixer looses entropy, the performance gains from the hash function may be lost.
+An example would be power-of-two modulo, which is a common mixer, but is known to be weak.
+One alternative would be the [fastrange](https://github.com/lemire/fastrange) by Daniel Lemire.
+Another one is the [Fibonacci hash trick](https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/) using the Golden Ratio, also used in StringZilla.
+
+### Unicode, UTF-8, and Wide Characters
+
+StringZilla does not __yet__ implement any Unicode-specific algorithms.
+The content is addressed at byte-level, and the string is assumed to be encoded in UTF-8 or extended ASCII.
+Refer to [simdutf](https://github.com/simdutf/simdutf) for fast conversions and [icu](https://github.com/unicode-org/icu) for character metadata.
+
+This may introduce frictions, when binding to some programming languages.
+Namely, Java, JavaScript, Python 2, C#, and Objective-C use wide characters (`wchar`) - two byte long codes.
+This leads [to all kinds of offset-counting issues][wide-char-offsets] when facing four-byte long Unicode characters.
+
+[wide-char-offsets]: https://josephg.com/blog/string-length-lies/
+
+## Contributing 👾
+
+Please check out the [contributing guide](CONTRIBUTING.md) for more details on how to setup the development environment and contribute to this project.
+If you like this project, you may also enjoy [USearch][usearch], [UCall][ucall], [UForm][uform], and [SimSIMD][simsimd]. 🤗
 
 [usearch]: https://github.com/unum-cloud/usearch
 [ucall]: https://github.com/unum-cloud/ucall
 [uform]: https://github.com/unum-cloud/uform
-[ustore]: https://github.com/unum-cloud/ustore
 [simsimd]: https://github.com/ashvardanian/simsimd
-[tenpack]: https://github.com/ashvardanian/tenpack
+
+## License 📜
+
+Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.
diff --git a/VERSION b/VERSION
index 50ffc5aa..2165f8f9 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-2.0.3
+2.0.4
diff --git a/assets/cover-strinzilla.jpeg b/assets/cover-strinzilla.jpeg
new file mode 100644
index 00000000..892b3342
Binary files /dev/null and b/assets/cover-strinzilla.jpeg differ
diff --git a/stringzilla.jpeg b/assets/meme-stringzilla-v2.jpeg
similarity index 100%
rename from stringzilla.jpeg
rename to assets/meme-stringzilla-v2.jpeg
diff --git a/assets/meme-stringzilla-v3.jpeg b/assets/meme-stringzilla-v3.jpeg
new file mode 100644
index 00000000..00230038
Binary files /dev/null and b/assets/meme-stringzilla-v3.jpeg differ
diff --git a/binding.gyp b/binding.gyp
index 338869bf..572036b6 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -3,8 +3,14 @@
         {
             "target_name": "stringzilla",
             "sources": ["javascript/lib.c"],
-            "include_dirs": ["stringzilla"],
-            "cflags": ["-std=c99", "-Wno-unknown-pragmas", "-Wno-maybe-uninitialized"],
+            "include_dirs": ["include"],
+            "cflags": [
+                "-std=c99",
+                "-Wno-unknown-pragmas",
+                "-Wno-maybe-uninitialized",
+                "-Wno-cast-function-type",
+                "-Wno-unused-function",
+            ],
         }
     ]
 }
diff --git a/build.rs b/build.rs
new file mode 100644
index 00000000..368600eb
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,14 @@
+fn main() {
+    cc::Build::new()
+        .file("c/lib.c")
+        .include("include")
+        .warnings(false)
+        .flag_if_supported("-std=c99")
+        .flag_if_supported("-fcolor-diagnostics")
+        .flag_if_supported("-fPIC")
+        .compile("stringzilla");
+
+    println!("cargo:rerun-if-changed=c/lib.c");
+    println!("cargo:rerun-if-changed=rust/lib.rs");
+    println!("cargo:rerun-if-changed=include/stringzilla/stringzilla.h");
+}
diff --git a/c/lib.c b/c/lib.c
new file mode 100644
index 00000000..61cfed97
--- /dev/null
+++ b/c/lib.c
@@ -0,0 +1,304 @@
+/**
+ *  @file       lib.c
+ *  @brief      StringZilla C library with dynamic backed dispatch for the most appropriate implementation.
+ *  @author     Ash Vardanian
+ *  @date       January 16, 2024
+ *  @copyright  Copyright (c) 2024
+ */
+#if defined(_WIN32) || defined(__CYGWIN__)
+#include <windows.h> // `DllMain`
+#endif
+
+// If we don't have the LibC, the `malloc` definition in `stringzilla.h` will be illformed.
+#if SZ_AVOID_LIBC
+typedef __SIZE_TYPE__ size_t;
+#endif
+
+// Overwrite `SZ_DYNAMIC_DISPATCH` before including StringZilla.
+#ifdef SZ_DYNAMIC_DISPATCH
+#undef SZ_DYNAMIC_DISPATCH
+#endif
+#define SZ_DYNAMIC_DISPATCH 1
+#include <stringzilla/stringzilla.h>
+
+#if SZ_AVOID_LIBC
+void free(void *start) { sz_unused(start); }
+void *malloc(size_t length) {
+    sz_unused(length);
+    return SZ_NULL;
+}
+#endif
+
+SZ_DYNAMIC sz_capability_t sz_capabilities(void) {
+
+#if SZ_USE_X86_AVX512 || SZ_USE_X86_AVX2
+
+    /// The states of 4 registers populated for a specific "cpuid" assembly call
+    union four_registers_t {
+        int array[4];
+        struct separate_t {
+            unsigned eax, ebx, ecx, edx;
+        } named;
+    } info1, info7;
+
+#ifdef _MSC_VER
+    __cpuidex(info1.array, 1, 0);
+    __cpuidex(info7.array, 7, 0);
+#else
+    __asm__ __volatile__("cpuid"
+                         : "=a"(info1.named.eax), "=b"(info1.named.ebx), "=c"(info1.named.ecx), "=d"(info1.named.edx)
+                         : "a"(1), "c"(0));
+    __asm__ __volatile__("cpuid"
+                         : "=a"(info7.named.eax), "=b"(info7.named.ebx), "=c"(info7.named.ecx), "=d"(info7.named.edx)
+                         : "a"(7), "c"(0));
+#endif
+
+    // Check for AVX2 (Function ID 7, EBX register)
+    // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L148
+    unsigned supports_avx2 = (info7.named.ebx & 0x00000020) != 0;
+    // Check for AVX512F (Function ID 7, EBX register)
+    // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L155
+    unsigned supports_avx512f = (info7.named.ebx & 0x00010000) != 0;
+    // Check for AVX512BW (Function ID 7, EBX register)
+    // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L166
+    unsigned supports_avx512bw = (info7.named.ebx & 0x40000000) != 0;
+    // Check for AVX512VL (Function ID 7, EBX register)
+    // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L167C25-L167C35
+    unsigned supports_avx512vl = (info7.named.ebx & 0x80000000) != 0;
+    // Check for GFNI (Function ID 1, ECX register)
+    // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L171C30-L171C40
+    unsigned supports_avx512vbmi = (info1.named.ecx & 0x00000002) != 0;
+    // Check for GFNI (Function ID 1, ECX register)
+    // https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L177C30-L177C40
+    unsigned supports_gfni = (info1.named.ecx & 0x00000100) != 0;
+
+    return (sz_capability_t)(                             //
+        (sz_cap_x86_avx2_k * supports_avx2) |             //
+        (sz_cap_x86_avx512f_k * supports_avx512f) |       //
+        (sz_cap_x86_avx512vl_k * supports_avx512vl) |     //
+        (sz_cap_x86_avx512bw_k * supports_avx512bw) |     //
+        (sz_cap_x86_avx512vbmi_k * supports_avx512vbmi) | //
+        (sz_cap_x86_gfni_k * (supports_gfni)) |           //
+        (sz_cap_serial_k));
+
+#endif // SIMSIMD_TARGET_X86
+
+#if SZ_USE_ARM_NEON || SZ_USE_ARM_SVE
+
+    // Every 64-bit Arm CPU supports NEON
+    unsigned supports_neon = 1;
+    unsigned supports_sve = 0;
+    unsigned supports_sve2 = 0;
+    sz_unused(supports_sve);
+    sz_unused(supports_sve2);
+
+    return (sz_capability_t)(                 //
+        (sz_cap_arm_neon_k * supports_neon) | //
+        (sz_cap_serial_k));
+
+#endif // SIMSIMD_TARGET_ARM
+
+    return sz_cap_serial_k;
+}
+
+typedef struct sz_implementations_t {
+    sz_equal_t equal;
+    sz_order_t order;
+
+    sz_move_t copy;
+    sz_move_t move;
+    sz_fill_t fill;
+
+    sz_find_byte_t find_byte;
+    sz_find_byte_t rfind_byte;
+    sz_find_t find;
+    sz_find_t rfind;
+    sz_find_set_t find_from_set;
+    sz_find_set_t rfind_from_set;
+
+    // TODO: Upcoming vectorization
+    sz_edit_distance_t edit_distance;
+    sz_alignment_score_t alignment_score;
+    sz_hashes_t hashes;
+
+} sz_implementations_t;
+static sz_implementations_t sz_dispatch_table;
+
+/**
+ *  @brief  Initializes a global static "virtual table" of supported backends
+ *          Run it just once to avoiding unnecessary `if`-s.
+ */
+static void sz_dispatch_table_init(void) {
+    sz_implementations_t *impl = &sz_dispatch_table;
+    sz_capability_t caps = sz_capabilities();
+    sz_unused(caps); //< Unused when compiling on pre-SIMD machines.
+
+    impl->equal = sz_equal_serial;
+    impl->order = sz_order_serial;
+    impl->copy = sz_copy_serial;
+    impl->move = sz_move_serial;
+    impl->fill = sz_fill_serial;
+
+    impl->find = sz_find_serial;
+    impl->rfind = sz_rfind_serial;
+    impl->find_byte = sz_find_byte_serial;
+    impl->rfind_byte = sz_rfind_byte_serial;
+    impl->find_from_set = sz_find_charset_serial;
+    impl->rfind_from_set = sz_rfind_charset_serial;
+
+    impl->edit_distance = sz_edit_distance_serial;
+    impl->alignment_score = sz_alignment_score_serial;
+    impl->hashes = sz_hashes_serial;
+
+#if SZ_USE_X86_AVX2
+    if (caps & sz_cap_x86_avx2_k) {
+        impl->copy = sz_copy_avx2;
+        impl->move = sz_move_avx2;
+        impl->fill = sz_fill_avx2;
+        impl->find_byte = sz_find_byte_avx2;
+        impl->rfind_byte = sz_rfind_byte_avx2;
+        impl->find = sz_find_avx2;
+        impl->rfind = sz_rfind_avx2;
+    }
+#endif
+
+#if SZ_USE_X86_AVX512
+    if (caps & sz_cap_x86_avx512f_k) {
+        impl->equal = sz_equal_avx512;
+        impl->order = sz_order_avx512;
+        impl->copy = sz_copy_avx512;
+        impl->move = sz_move_avx512;
+        impl->fill = sz_fill_avx512;
+
+        impl->find = sz_find_avx512;
+        impl->rfind = sz_rfind_avx512;
+        impl->find_byte = sz_find_byte_avx512;
+        impl->rfind_byte = sz_rfind_byte_avx512;
+
+        impl->edit_distance = sz_edit_distance_avx512;
+    }
+
+    if ((caps & sz_cap_x86_avx512f_k) && (caps & sz_cap_x86_avx512vl_k) && (caps & sz_cap_x86_gfni_k) &&
+        (caps & sz_cap_x86_avx512bw_k) && (caps & sz_cap_x86_avx512vbmi_k)) {
+        impl->find_from_set = sz_find_charset_avx512;
+        impl->rfind_from_set = sz_rfind_charset_avx512;
+        impl->alignment_score = sz_alignment_score_avx512;
+    }
+#endif
+
+#if SZ_USE_ARM_NEON
+    if (caps & sz_cap_arm_neon_k) {
+        impl->find = sz_find_neon;
+        impl->rfind = sz_rfind_neon;
+        impl->find_byte = sz_find_byte_neon;
+        impl->rfind_byte = sz_rfind_byte_neon;
+        impl->find_from_set = sz_find_charset_neon;
+        impl->rfind_from_set = sz_rfind_charset_neon;
+    }
+#endif
+}
+
+#if defined(_MSC_VER)
+BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
+    switch (fdwReason) {
+    case DLL_PROCESS_ATTACH: sz_dispatch_table_init(); return TRUE;
+    case DLL_THREAD_ATTACH: return TRUE;
+    case DLL_THREAD_DETACH: return TRUE;
+    case DLL_PROCESS_DETACH: return TRUE;
+    }
+}
+#else
+__attribute__((constructor)) static void sz_dispatch_table_init_on_gcc_or_clang(void) { sz_dispatch_table_init(); }
+#endif
+
+SZ_DYNAMIC sz_bool_t sz_equal(sz_cptr_t a, sz_cptr_t b, sz_size_t length) {
+    return sz_dispatch_table.equal(a, b, length);
+}
+
+SZ_DYNAMIC sz_ordering_t sz_order(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length) {
+    return sz_dispatch_table.order(a, a_length, b, b_length);
+}
+
+SZ_DYNAMIC void sz_copy(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    sz_dispatch_table.copy(target, source, length);
+}
+
+SZ_DYNAMIC void sz_move(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    sz_dispatch_table.move(target, source, length);
+}
+
+SZ_DYNAMIC void sz_fill(sz_ptr_t target, sz_size_t length, sz_u8_t value) {
+    sz_dispatch_table.fill(target, length, value);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_byte(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle) {
+    return sz_dispatch_table.find_byte(haystack, h_length, needle);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_byte(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle) {
+    return sz_dispatch_table.rfind_byte(haystack, h_length, needle);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length) {
+    return sz_dispatch_table.find(haystack, h_length, needle, n_length);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length) {
+    return sz_dispatch_table.rfind(haystack, h_length, needle, n_length);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_charset(sz_cptr_t text, sz_size_t length, sz_charset_t const *set) {
+    return sz_dispatch_table.find_from_set(text, length, set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_charset(sz_cptr_t text, sz_size_t length, sz_charset_t const *set) {
+    return sz_dispatch_table.rfind_from_set(text, length, set);
+}
+
+SZ_DYNAMIC sz_size_t sz_edit_distance( //
+    sz_cptr_t a, sz_size_t a_length,   //
+    sz_cptr_t b, sz_size_t b_length,   //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+    return sz_dispatch_table.edit_distance(a, a_length, b, b_length, bound, alloc);
+}
+
+SZ_DYNAMIC sz_ssize_t sz_alignment_score(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
+                                         sz_error_cost_t const *subs, sz_error_cost_t gap,
+                                         sz_memory_allocator_t *alloc) {
+    return sz_dispatch_table.alignment_score(a, a_length, b, b_length, subs, gap, alloc);
+}
+
+SZ_DYNAMIC void sz_hashes(sz_cptr_t text, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                          sz_hash_callback_t callback, void *callback_handle) {
+    sz_dispatch_table.hashes(text, length, window_length, step, callback, callback_handle);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_char_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    return sz_find_charset(h, h_length, &set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    sz_charset_invert(&set);
+    return sz_find_charset(h, h_length, &set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_char_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    return sz_rfind_charset(h, h_length, &set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    sz_charset_invert(&set);
+    return sz_rfind_charset(h, h_length, &set);
+}
diff --git a/example.ipynb b/example.ipynb
new file mode 100644
index 00000000..1afed869
--- /dev/null
+++ b/example.ipynb
@@ -0,0 +1,120 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Requirement already satisfied: stringzilla in /home/ubuntu/miniconda3/lib/python3.11/site-packages (2.0.3)\n"
+     ]
+    }
+   ],
+   "source": [
+    "!pip install stringzilla"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "text = open(\"leipzig1M.txt\", \"r\").read()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "21191455"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# Read a textual file, tokenize into words, sort them alphabetically, and print the most common one\n",
+    "words = text.split()\n",
+    "len(words)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "first_offsets = []\n",
+    "for word in words[-1_000:]:\n",
+    "    first_offsets.append(text.find(word))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from stringzilla import Str"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "text_sz = Str(text)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "first_offsets_sz = []\n",
+    "for word in words[-10:]:\n",
+    "    first_offsets_sz.append(text_sz.find(word))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "base",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/include/stringzilla/experimental.h b/include/stringzilla/experimental.h
new file mode 100644
index 00000000..93fc1ff3
--- /dev/null
+++ b/include/stringzilla/experimental.h
@@ -0,0 +1,724 @@
+/**
+ *  @brief  Experimental kernels for StringZilla.
+ *  @file   experimental.h
+ *  @author Ash Vardanian
+ */
+#ifndef STRINGZILLA_EXPERIMENTAL_H_
+#define STRINGZILLA_EXPERIMENTAL_H_
+
+#include "stringzilla.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  @brief  Bitap algo for exact matching of patterns up to @b 8-bytes long.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_8bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                        sz_size_t n_length) {
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *h_end = h_unsigned + h_length;
+
+    // Here is our baseline:
+    //
+    //      sz_u8_t running_match = 0xFF;
+    //      sz_u8_t character_position_masks[256];
+    //      for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFF; }
+    //      for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
+    //      for (sz_size_t i = 0; i < h_length; ++i) {
+    //          running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
+    //          if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
+    //      }
+    //
+    // On very short patterns, however, every tiny condition may have a huge affect on performance.
+    // 1. Let's replace byte-level intialization of `character_position_masks` with 64-bit ops.
+    // 2. Let's combine the first `n_length - 1` passes of the last loop into the previous loop.
+    typedef sz_u8_t offset_mask_t;
+
+    // Initialize the possible offset masks.
+    // Even using 8-byte `wide_masks` words, this would require 64 iterations to populate 256 bytes.
+    union {
+        offset_mask_t masks[256];
+        sz_u64_t wide_masks[sizeof(offset_mask_t) * 256 / sizeof(sz_u64_t)];
+    } character_positions;
+    for (sz_size_t i = 0; i != sizeof(offset_mask_t) * 256 / sizeof(sz_u64_t); ++i) {
+        character_positions.wide_masks[i] = 0xFFFFFFFFFFFFFFFFull;
+    }
+
+    // Populate the mask with possible positions for each character.
+    for (sz_size_t i = 0; i != n_length; ++i) { character_positions.masks[n_unsigned[i]] &= ~((offset_mask_t)1 << i); }
+
+    // The "running match" for the serial algorithm should be at least as wide as the `offset_mask_t`.
+    // But on modern systems larger integers may work better.
+    offset_mask_t running_match = 0, final_match = 1;
+    running_match = ~(running_match ^ running_match); //< Initialize with all-ones
+    final_match <<= n_length - 1;
+
+    for (; (h_unsigned != h_end) + ((running_match & final_match) != 0) == 2; ++h_unsigned) {
+        running_match = (running_match << 1) | character_positions.masks[h_unsigned[0]];
+    }
+    return ((running_match & final_match) == 0) ? (sz_cptr_t)(h_unsigned - n_length) : NULL;
+}
+
+/**
+ *  @brief  Bitap algorithm for exact matching of patterns up to @b 8-bytes long in @b reverse order.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_8bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                         sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u8_t running_match = 0xFF;
+    sz_u8_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFF; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
+        if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algo for exact matching of patterns up to @b 16-bytes long.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_16bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                         sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u16_t running_match = 0xFFFF;
+    sz_u16_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFF; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
+        if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algorithm for exact matching of patterns up to @b 16-bytes long in @b reverse order.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_16bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                          sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u16_t running_match = 0xFFFF;
+    sz_u16_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFF; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
+        if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algo for exact matching of patterns up to @b 32-bytes long.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_32bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                         sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u32_t running_match = 0xFFFFFFFF;
+    sz_u32_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFF; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
+        if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algorithm for exact matching of patterns up to @b 32-bytes long in @b reverse order.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_32bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                          sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u32_t running_match = 0xFFFFFFFF;
+    sz_u32_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFF; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
+        if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algo for exact matching of patterns up to @b 64-bytes long.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                         sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
+    sz_u64_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1ull << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
+        if ((running_match & (1ull << (n_length - 1))) == 0) { return h + i - n_length + 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algorithm for exact matching of patterns up to @b 64-bytes long in @b reverse order.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                          sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
+    sz_u64_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1ull << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
+        if ((running_match & (1ull << (n_length - 1))) == 0) { return h + h_length - i - 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algo for approximate matching of patterns up to @b 64-bytes long.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_bounded_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                                 sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
+    sz_u64_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1ull << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
+        if ((running_match & (1ull << (n_length - 1))) == 0) { return h + i - n_length + 1; }
+    }
+
+    return NULL;
+}
+
+/**
+ *  @brief  Bitap algorithm for approximate matching of patterns up to @b 64-bytes long in @b reverse order.
+ *          https://en.wikipedia.org/wiki/Bitap_algorithm
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_bounded_last_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                                      sz_size_t n_length) {
+    sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
+    sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
+    sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
+    sz_u64_t character_position_masks[256];
+    for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
+    for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1ull << i); }
+    for (sz_size_t i = 0; i < h_length; ++i) {
+        running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
+        if ((running_match & (1ull << (n_length - 1))) == 0) { return h + h_length - i - 1; }
+    }
+
+    return NULL;
+}
+
+#if SZ_USE_AVX512
+
+SZ_PUBLIC sz_size_t sz_edit_distance_avx512(     //
+    sz_cptr_t const a, sz_size_t const a_length, //
+    sz_cptr_t const b, sz_size_t const b_length, //
+    sz_size_t const bound, sz_memory_allocator_t *alloc) {
+
+    sz_u512_vec_t a_vec, b_vec, previous_vec, current_vec, permutation_vec;
+    sz_u512_vec_t cost_deletion_vec, cost_insertion_vec, cost_substitution_vec;
+    sz_size_t min_distance;
+
+    b_vec.zmm = _mm512_maskz_loadu_epi8(_sz_u64_mask_until(b_length), b);
+    previous_vec.zmm = _mm512_set_epi8(63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, //
+                                       47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, //
+                                       31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, //
+                                       15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
+
+    // Shifting bytes across the whole ZMM register is quite complicated, so let's use a permutation for that.
+    permutation_vec.zmm = _mm512_set_epi8(62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, //
+                                          46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, //
+                                          30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, //
+                                          14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 63);
+
+    for (sz_size_t idx_a = 0; idx_a != a_length; ++idx_a) {
+        min_distance = bound - 1;
+
+        a_vec.zmm = _mm512_set1_epi8(a[idx_a]);
+        // We first start by computing the cost of deletions and substitutions
+        // for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
+        //     sz_u8_t cost_deletion = previous_vec.u8s[idx_b + 1] + 1;
+        //     sz_u8_t cost_substitution = previous_vec.u8s[idx_b] + (a[idx_a] != b[idx_b]);
+        //     current_vec.u8s[idx_b + 1] = sz_min_of_two(cost_deletion, cost_substitution);
+        // }
+        cost_deletion_vec.zmm = _mm512_add_epi8(previous_vec.zmm, _mm512_set1_epi8(1));
+        cost_substitution_vec.zmm =
+            _mm512_mask_set1_epi8(_mm512_setzero_si512(), _mm512_cmpneq_epi8_mask(a_vec.zmm, b_vec.zmm), 0x01);
+        cost_substitution_vec.zmm = _mm512_add_epi8(previous_vec.zmm, cost_substitution_vec.zmm);
+        cost_substitution_vec.zmm = _mm512_permutexvar_epi8(permutation_vec.zmm, cost_substitution_vec.zmm);
+        current_vec.zmm = _mm512_min_epu8(cost_deletion_vec.zmm, cost_substitution_vec.zmm);
+        current_vec.u8s[0] = idx_a + 1;
+
+        // Now we need to compute the inclusive prefix sums using the minimum operator
+        // In one line:
+        //      current_vec.u8s[idx_b + 1] = sz_min_of_two(current_vec.u8s[idx_b + 1], current_vec.u8s[idx_b] + 1)
+        //
+        // Unrolling this:
+        //      current_vec.u8s[0 + 1] = sz_min_of_two(current_vec.u8s[0 + 1], current_vec.u8s[0] + 1)
+        //      current_vec.u8s[1 + 1] = sz_min_of_two(current_vec.u8s[1 + 1], current_vec.u8s[1] + 1)
+        //      current_vec.u8s[2 + 1] = sz_min_of_two(current_vec.u8s[2 + 1], current_vec.u8s[2] + 1)
+        //      current_vec.u8s[3 + 1] = sz_min_of_two(current_vec.u8s[3 + 1], current_vec.u8s[3] + 1)
+        //
+        // Alternatively, using a tree-like reduction in log2 steps:
+        //      - 6 cycles of reductions shifting by 1, 2, 4, 8, 16, 32, 64 bytes;
+        //      - with each cycle containing at least one shift, min, add, blend.
+        //
+        // Which adds meaningless complexity without any performance gains.
+        for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
+            sz_u8_t cost_insertion = current_vec.u8s[idx_b] + 1;
+            current_vec.u8s[idx_b + 1] = sz_min_of_two(current_vec.u8s[idx_b + 1], cost_insertion);
+        }
+
+        // Swap previous_distances and current_distances pointers
+        sz_u512_vec_t temp_vec;
+        temp_vec.zmm = previous_vec.zmm;
+        previous_vec.zmm = current_vec.zmm;
+        current_vec.zmm = temp_vec.zmm;
+    }
+
+    return previous_vec.u8s[b_length] < bound ? previous_vec.u8s[b_length] : bound;
+}
+
+sz_u512_vec_t sz_inclusive_min(sz_i32_t previous, sz_error_cost_t gap, sz_u512_vec_t base_vec) {
+
+    sz_u512_vec_t gap_vec, gap_double_vec, gap_quad_vec, gap_octa_vec;
+    gap_vec.zmm = _mm512_set1_epi32(gap);
+    gap_double_vec.zmm = _mm512_set1_epi32(2 * gap);
+    gap_quad_vec.zmm = _mm512_set1_epi32(4 * gap);
+    gap_octa_vec.zmm = _mm512_set1_epi32(8 * gap);
+
+    // __mmask16 mask_skip_one = 0xFFFF - 1;
+    // __mmask16 mask_skip_two = 0xFFFF - 3;
+    // __mmask16 mask_skip_four = 0xFFF0;
+    // __mmask16 mask_skip_eight = 0xFF00;
+    __mmask16 mask_skip_one = 0x7FFF;
+    __mmask16 mask_skip_two = 0x3FFF;
+    __mmask16 mask_skip_four = 0x0FFF;
+    __mmask16 mask_skip_eight = 0x00FF;
+    sz_u512_vec_t shift_by_one_vec, shift_by_two_vec, shift_by_four_vec, shift_by_eight_vec;
+    shift_by_one_vec.zmm = _mm512_set_epi32(14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0);
+    shift_by_two_vec.zmm = _mm512_set_epi32(13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0);
+    shift_by_four_vec.zmm = _mm512_set_epi32(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0);
+    shift_by_eight_vec.zmm = _mm512_set_epi32(7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+    sz_u512_vec_t shifted_vec;
+    sz_u512_vec_t new_vec = base_vec;
+    shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_one_vec.zmm, new_vec.zmm);
+    shifted_vec.i32s[0] = previous;
+    shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_vec.zmm);
+    new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_one, new_vec.zmm, shifted_vec.zmm);
+    sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
+
+    shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_two_vec.zmm, new_vec.zmm);
+    shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_double_vec.zmm);
+    new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_two, new_vec.zmm, shifted_vec.zmm);
+    sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
+
+    shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_four_vec.zmm, new_vec.zmm);
+    shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_quad_vec.zmm);
+    new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_four, new_vec.zmm, shifted_vec.zmm);
+    sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
+
+    shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_eight_vec.zmm, new_vec.zmm);
+    shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_octa_vec.zmm);
+    new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_eight, new_vec.zmm, shifted_vec.zmm);
+
+    sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
+    for (sz_size_t i = 1; i < 16; i++) sz_assert(new_vec.i32s[i] == max(new_vec.i32s[i - 1] + gap, new_vec.i32s[i]));
+
+    return new_vec;
+}
+
+#endif // SZ_USE_AVX512
+
+#if SZ_USE_ARM_NEON
+
+SZ_INTERNAL void interleave_uint32x4_to_uint64x2(uint32x4_t in_low, uint32x4_t in_high, uint64x2_t *out_first_second,
+                                                 uint64x2_t *out_third_fourth) {
+    // Interleave elements
+    uint32x4x2_t interleaved = vzipq_u32(in_low, in_high);
+
+    // The results are now in two uint32x4_t vectors, which we need to cast to uint64x2_t
+    *out_first_second = vreinterpretq_u64_u32(interleaved.val[0]);
+    *out_third_fourth = vreinterpretq_u64_u32(interleaved.val[1]);
+}
+
+/*  Arm NEON has several very relevant extensions for 32-bit FMA we can use for rolling hashes:
+ *  * vmlaq_u32 - vector "fused-multiply-add"
+ *  * vmlaq_n_u32 - vector-scalar "fused-multiply-add"
+ *  * vmlsq_u32 - vector "fused-multiply-subtract"
+ *  * vmlsq_n_u32 - vector-scalar "fused-multiply-subtract"
+ *  Other basic intrinsics worth remembering:
+ *  * vbslq_u32 - bitwise select to avoid branching
+ *  * vld1q_dup_u32 - broadcast a 32-bit word into all 4 lanes of a 128-bit register
+ */
+
+SZ_PUBLIC void sz_hashes_neon_naive(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                                    sz_hash_callback_t callback, void *callback_handle) {
+
+    if (length < window_length || !window_length) return;
+    if (length < 2 * window_length) {
+        sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
+        return;
+    }
+
+    // Using NEON, we can perform 4 integer multiplications and additions within one register.
+    // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
+    sz_u8_t const *text = (sz_u8_t const *)start;
+    sz_u8_t const *text_end = text + length;
+
+    // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
+    sz_u32_t prime_power_low = 1, prime_power_high = 1;
+    for (sz_size_t i = 0; i + 1 < window_length; ++i)
+        prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
+        prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
+
+    sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
+    uint8_t high_shift = 77u;
+    uint32_t prime = SZ_U32_MAX_PRIME;
+
+    sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
+    // Let's skip the first window, as we are going to compute it in the loop.
+    sz_size_t cycles = 0;
+    sz_size_t step_mask = step - 1;
+    sz_u32_t one = 1;
+
+    // In every iteration we process 4 consecutive sliding windows.
+    // Once each of them computes separate values, we step forward (W-1) times,
+    // computing all interleaving values. That way the byte spilled from the second
+    // hash, can be added to the first one. That way we minimize the number of separate loads.
+    for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
+        hash_low_vec.u32x4 = vld1q_dup_u32(&one);
+        hash_high_vec.u32x4 = vld1q_dup_u32(&one);
+        for (sz_size_t i = 0; i != window_length; ++i) {
+            chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
+            chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
+            chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
+            chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
+            chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
+
+            // Append new data.
+            hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
+            hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
+            hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
+                                           vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
+            hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
+                                            vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
+        }
+
+        if ((cycles & step_mask) == 0) {
+            interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
+                                            &hash_mix23_vec.u64x2);
+            callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
+        }
+        ++cycles;
+
+        for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
+            // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
+            // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
+            chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
+            chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
+            chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
+            chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
+            chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
+            chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
+            chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
+
+            // Drop old data.
+            hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
+            hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
+
+            // Append new data.
+            hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
+            hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
+            hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
+                                           vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
+            hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
+                                            vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
+            // Mix and call the user if needed
+            if ((cycles & step_mask) == 0) {
+                interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
+                                                &hash_mix23_vec.u64x2);
+                callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
+                callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
+                callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
+                callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
+            }
+        }
+    }
+}
+
+SZ_PUBLIC void sz_hashes_neon_reusing_loads(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
+                                            sz_hash_callback_t callback, void *callback_handle) {
+
+    if (length < window_length || !window_length) return;
+    if (length < 2 * window_length) {
+        sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
+        return;
+    }
+
+    // Using NEON, we can perform 4 integer multiplications and additions within one register.
+    // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
+    sz_u8_t const *text = (sz_u8_t const *)start;
+    sz_u8_t const *text_end = text + length;
+
+    // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
+    sz_u32_t prime_power_low = 1, prime_power_high = 1;
+    for (sz_size_t i = 0; i + 1 < window_length; ++i)
+        prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
+        prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
+
+    sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
+    uint8_t high_shift = 77u;
+    uint32_t prime = SZ_U32_MAX_PRIME;
+
+    sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
+    // Let's skip the first window, as we are going to compute it in the loop.
+    sz_size_t cycles = 0;
+    sz_size_t const step_mask = step - 1;
+    sz_u32_t const one = 1;
+
+    // In every iteration we process 4 consecutive sliding windows.
+    // Once each of them computes separate values, we step forward (W-1) times,
+    // computing all interleaving values. That way the byte spilled from the second
+    // hash, can be added to the first one. That way we minimize the number of separate loads.
+    for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
+        hash_low_vec.u32x4 = vld1q_dup_u32(&one);
+        hash_high_vec.u32x4 = vld1q_dup_u32(&one);
+        for (sz_size_t i = 0; i != window_length; ++i) {
+            chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
+            chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
+            chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
+            chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
+            chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
+
+            // Append new data.
+            hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
+            hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
+            hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
+                                           vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
+            hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
+                                            vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
+        }
+
+        if ((cycles & step_mask) == 0) {
+            interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
+                                            &hash_mix23_vec.u64x2);
+            callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
+        }
+        ++cycles;
+
+        for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
+            // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
+            // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
+            // Vectorizing these loads is a huge opportunity for performance optimizations, but naive prefetching
+            // into the register just makes things worse.
+            chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
+            chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
+            chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
+            chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
+            chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
+            chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
+            chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
+
+            // Drop old data.
+            hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
+            hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
+
+            // Append new data.
+            hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
+            hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
+            hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
+                                           vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
+            hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
+                                            vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
+
+            // Mix and call the user if needed
+            if ((cycles & step_mask) == 0) {
+                interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
+                                                &hash_mix23_vec.u64x2);
+                callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
+                callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
+                callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
+                callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
+            }
+        }
+    }
+}
+
+SZ_PUBLIC void sz_hashes_neon_readahead(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
+                                       sz_hash_callback_t callback, void *callback_handle) {
+
+    if (length < window_length || !window_length) return;
+    if (length < 2 * window_length) {
+        sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
+        return;
+    }
+
+    // Using NEON, we can perform 4 integer multiplications and additions within one register.
+    // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
+    sz_u8_t const *text = (sz_u8_t const *)start;
+    sz_u8_t const *text_end = text + length;
+
+    // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
+    sz_u32_t prime_power_low = 1, prime_power_high = 1;
+    for (sz_size_t i = 0; i + 1 < window_length; ++i)
+        prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
+        prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
+
+    sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
+    uint8_t high_shift = 77u;
+    uint32_t prime = SZ_U32_MAX_PRIME;
+
+    /// Primary buffers containing four upcasted characters as uint32_t values.
+    sz_u128_vec_t chars_outgoing_low_vec, chars_incoming_low_vec;
+    sz_u128_vec_t chars_outgoing_high_vec, chars_incoming_high_vec;
+    // Let's skip the first window, as we are going to compute it in the loop.
+    sz_size_t cycles = 0;
+    sz_size_t const step_mask = step - 1;
+    sz_u32_t const one = 1;
+
+    // In every iteration we process 4 consecutive sliding windows.
+    // Once each of them computes separate values, we step forward (W-1) times,
+    // computing all interleaving values. That way the byte spilled from the second
+    // hash, can be added to the first one. That way we minimize the number of separate loads.
+    sz_size_t read_ahead_length = window_length - 1 + 16; // TODO: Instead of +16 round up to 16 multiple
+    for (; text + window_length * 4 + read_ahead_length <= text_end; text += window_length * 4) {
+        hash_low_vec.u32x4 = vld1q_dup_u32(&one);
+        hash_high_vec.u32x4 = vld1q_dup_u32(&one);
+
+        for (sz_size_t i = 0; i < window_length;) {
+            sz_u128_vec_t chars_readahead_vec[4];
+            chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
+            chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
+            chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
+            chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
+
+            for (; i != window_length; ++i) {
+                chars_incoming_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
+                chars_incoming_low_vec.u32s[1] = chars_readahead_vec[1].u8x16[i];
+                chars_incoming_low_vec.u32s[2] = chars_readahead_vec[2].u8x16[i];
+                chars_incoming_low_vec.u32s[3] = chars_readahead_vec[3].u8x16[i];
+                chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
+
+                // Append new data.
+                hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
+                hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
+                hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
+                                               vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
+                hash_high_vec.u32x4 =
+                    vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
+                              vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
+            }
+        }
+
+        if ((cycles & step_mask) == 0) {
+            interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
+                                            &hash_mix23_vec.u64x2);
+            callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
+        }
+        ++cycles;
+
+        for (sz_size_t i = 0; i + 1 < window_length; ++i, ++cycles) {
+            // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
+            // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
+            sz_u128_vec_t chars_readahead_vec[5];
+            chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
+            chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
+            chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
+            chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
+            chars_readahead_vec[4].u8x16 = vld1q_u8(text + window_length * 4 + i);
+
+            for (; i + 1 < window_length; ++i) {
+                // Transpose
+                chars_outgoing_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
+                chars_outgoing_low_vec.u32s[1] = chars_incoming_low_vec.u32s[0] = chars_readahead_vec[1].u8x16[i];
+                chars_outgoing_low_vec.u32s[2] = chars_incoming_low_vec.u32s[1] = chars_readahead_vec[2].u8x16[i];
+                chars_outgoing_low_vec.u32s[3] = chars_incoming_low_vec.u32s[2] = chars_readahead_vec[3].u8x16[i];
+                chars_incoming_low_vec.u32s[3] = chars_readahead_vec[4].u8x16[i];
+
+                chars_outgoing_high_vec.u8x16 = vaddq_u8(chars_outgoing_low_vec.u8x16, vld1q_dup_u8(&high_shift));
+                chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
+
+                // Drop old data.
+                hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_low_vec.u32x4, prime_power_low);
+                hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_high_vec.u32x4, prime_power_high);
+
+                // Append new data.
+                hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
+                hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
+                hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
+                                               vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
+                hash_high_vec.u32x4 =
+                    vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
+                              vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
+
+                // Mix and call the user if needed
+                if ((cycles & step_mask) == 0) {
+                    interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
+                                                    &hash_mix23_vec.u64x2);
+                    callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0],
+                             callback_handle);
+                    callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1],
+                             callback_handle);
+                    callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0],
+                             callback_handle);
+                    callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1],
+                             callback_handle);
+                }
+            }
+        }
+    }
+}
+
+#endif // SZ_USE_ARM_NEON
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // STRINGZILLA_EXPERIMENTAL_H_
\ No newline at end of file
diff --git a/include/stringzilla/module.modulemap b/include/stringzilla/module.modulemap
new file mode 100644
index 00000000..b270445c
--- /dev/null
+++ b/include/stringzilla/module.modulemap
@@ -0,0 +1,9 @@
+module StringZillaC {
+    header "stringzilla.h"
+    export *
+}
+
+module StringZillaCpp {
+    header "stringzilla.hpp"
+    export *
+}
diff --git a/include/stringzilla/stringzilla.h b/include/stringzilla/stringzilla.h
new file mode 100644
index 00000000..82672aa6
--- /dev/null
+++ b/include/stringzilla/stringzilla.h
@@ -0,0 +1,4778 @@
+/**
+ *  @brief  StringZilla is a collection of simple string algorithms, designed to be used in Big Data applications.
+ *          It may be slower than LibC, but has a broader & cleaner interface, and a very short implementation
+ *          targeting modern x86 CPUs with AVX-512 and Arm NEON and older CPUs with SWAR and auto-vectorization.
+ *
+ *  Consider overriding the following macros to customize the library:
+ *
+ *  - `SZ_DEBUG=0` - whether to enable debug assertions and logging.
+ *  - `SZ_DYNAMIC_DISPATCH=0` - whether to use runtime dispatching of the most advanced SIMD backend.
+ *  - `SZ_USE_MISALIGNED_LOADS=0` - whether to use misaligned loads on platforms that support them.
+ *  - `SZ_SWAR_THRESHOLD=24` - threshold for switching to SWAR backend over serial byte-level for-loops.
+ *  - `SZ_USE_X86_AVX512=?` - whether to use AVX-512 instructions on x86_64.
+ *  - `SZ_USE_X86_AVX2=?` - whether to use AVX2 instructions on x86_64.
+ *  - `SZ_USE_ARM_NEON=?` - whether to use NEON instructions on ARM.
+ *  - `SZ_USE_ARM_SVE=?` - whether to use SVE instructions on ARM.
+ *
+ *  @see    StringZilla: https://github.com/ashvardanian/StringZilla/blob/main/README.md
+ *  @see    LibC String: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html
+ *
+ *  @file   stringzilla.h
+ *  @author Ash Vardanian
+ */
+#ifndef STRINGZILLA_H_
+#define STRINGZILLA_H_
+
+#define STRINGZILLA_VERSION_MAJOR 2
+#define STRINGZILLA_VERSION_MINOR 0
+#define STRINGZILLA_VERSION_PATCH 4
+
+/**
+ *  @brief  When set to 1, the library will include the following LibC headers: <stddef.h> and <stdint.h>.
+ *          In debug builds (SZ_DEBUG=1), the library will also include <stdio.h> and <stdlib.h>.
+ *
+ *  You may want to disable this compiling for use in the kernel, or in embedded systems.
+ *  You may also avoid them, if you are very sensitive to compilation time and avoid pre-compiled headers.
+ *  https://artificial-mind.net/projects/compile-health/
+ */
+#ifndef SZ_AVOID_LIBC
+#define SZ_AVOID_LIBC (0) // true or false
+#endif
+
+/**
+ *  @brief  A misaligned load can be - trying to fetch eight consecutive bytes from an address
+ *          that is not divisible by eight.
+ *
+ *  Most platforms support it, but there is no industry standard way to check for those.
+ *  This value will mostly affect the performance of the serial (SWAR) backend.
+ */
+#ifndef SZ_USE_MISALIGNED_LOADS
+#define SZ_USE_MISALIGNED_LOADS (0) // true or false
+#endif
+
+/**
+ *  @brief  Removes compile-time dispatching, and replaces it with runtime dispatching.
+ *          So the `sz_find` function will invoke the most advanced backend supported by the CPU,
+ *          that runs the program, rather than the most advanced backend supported by the CPU
+ *          used to compile the library or the downstream application.
+ */
+#ifndef SZ_DYNAMIC_DISPATCH
+#define SZ_DYNAMIC_DISPATCH (0) // true or false
+#endif
+
+/**
+ *  @brief  Analogous to `size_t` and `std::size_t`, unsigned integer, identical to pointer size.
+ *          64-bit on most platforms where pointers are 64-bit.
+ *          32-bit on platforms where pointers are 32-bit.
+ */
+#if defined(__LP64__) || defined(_LP64) || defined(__x86_64__) || defined(_WIN64)
+#define SZ_DETECT_64_BIT (1)
+#define SZ_SIZE_MAX (0xFFFFFFFFFFFFFFFFull)  // Largest unsigned integer that fits into 64 bits.
+#define SZ_SSIZE_MAX (0x7FFFFFFFFFFFFFFFull) // Largest signed integer that fits into 64 bits.
+#else
+#define SZ_DETECT_64_BIT (0)
+#define SZ_SIZE_MAX (0xFFFFFFFFu)  // Largest unsigned integer that fits into 32 bits.
+#define SZ_SSIZE_MAX (0x7FFFFFFFu) // Largest signed integer that fits into 32 bits.
+#endif
+
+/*
+ *  Debugging and testing.
+ */
+#ifndef SZ_DEBUG
+#if defined(DEBUG) || defined(_DEBUG) // This means "Not using DEBUG information".
+#define SZ_DEBUG (1)
+#else
+#define SZ_DEBUG (0)
+#endif
+#endif
+
+/*  Annotation for the public API symbols:
+ *
+ *  - `SZ_PUBLIC` is used for functions that are part of the public API.
+ *  - `SZ_INTERNAL` is used for internal helper functions with unstable APIs.
+ *  - `SZ_DYNAMIC` is used for functions that are part of the public API, but are dispatched at runtime.
+ */
+#ifndef SZ_DYNAMIC
+#if SZ_DYNAMIC_DISPATCH
+#if defined(_WIN32) || defined(__CYGWIN__)
+#define SZ_DYNAMIC __declspec(dllexport)
+#define SZ_PUBLIC inline static
+#define SZ_INTERNAL inline static
+#else
+#define SZ_DYNAMIC __attribute__((visibility("default")))
+#define SZ_PUBLIC __attribute__((unused)) inline static
+#define SZ_INTERNAL __attribute__((always_inline)) inline static
+#endif // _WIN32 || __CYGWIN__
+#else
+#define SZ_DYNAMIC inline static
+#define SZ_PUBLIC inline static
+#define SZ_INTERNAL inline static
+#endif // SZ_DYNAMIC_DISPATCH
+#endif // SZ_DYNAMIC
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ *  Let's infer the integer types or pull them from LibC,
+ *  if that is allowed by the user.
+ */
+#if !SZ_AVOID_LIBC
+#include <stddef.h>           // `size_t`
+#include <stdint.h>           // `uint8_t`
+typedef int8_t sz_i8_t;       // Always 8 bits
+typedef uint8_t sz_u8_t;      // Always 8 bits
+typedef uint16_t sz_u16_t;    // Always 16 bits
+typedef int32_t sz_i32_t;     // Always 32 bits
+typedef uint32_t sz_u32_t;    // Always 32 bits
+typedef uint64_t sz_u64_t;    // Always 64 bits
+typedef int64_t sz_i64_t;     // Always 64 bits
+typedef size_t sz_size_t;     // Pointer-sized unsigned integer, 32 or 64 bits
+typedef ptrdiff_t sz_ssize_t; // Signed version of `sz_size_t`, 32 or 64 bits
+
+#else // if SZ_AVOID_LIBC:
+
+typedef signed char sz_i8_t;         // Always 8 bits
+typedef unsigned char sz_u8_t;       // Always 8 bits
+typedef unsigned short sz_u16_t;     // Always 16 bits
+typedef int sz_i32_t;                // Always 32 bits
+typedef unsigned int sz_u32_t;       // Always 32 bits
+typedef long long sz_i64_t;          // Always 64 bits
+typedef unsigned long long sz_u64_t; // Always 64 bits
+
+#if SZ_DETECT_64_BIT
+typedef unsigned long long sz_size_t; // 64-bit.
+typedef long long sz_ssize_t;         // 64-bit.
+#else
+typedef unsigned sz_size_t;  // 32-bit.
+typedef unsigned sz_ssize_t; // 32-bit.
+#endif // SZ_DETECT_64_BIT
+
+#endif // SZ_AVOID_LIBC
+
+/**
+ *  @brief  Compile-time assert macro similar to `static_assert` in C++.
+ */
+#define sz_static_assert(condition, name)                \
+    typedef struct {                                     \
+        int static_assert_##name : (condition) ? 1 : -1; \
+    } sz_static_assert_##name##_t
+
+sz_static_assert(sizeof(sz_size_t) == sizeof(void *), sz_size_t_must_be_pointer_size);
+sz_static_assert(sizeof(sz_ssize_t) == sizeof(void *), sz_ssize_t_must_be_pointer_size);
+
+#pragma region Public API
+
+typedef char *sz_ptr_t;          // A type alias for `char *`
+typedef char const *sz_cptr_t;   // A type alias for `char const *`
+typedef sz_i8_t sz_error_cost_t; // Character mismatch cost for fuzzy matching functions
+
+typedef enum { sz_false_k = 0, sz_true_k = 1 } sz_bool_t;                        // Only one relevant bit
+typedef enum { sz_less_k = -1, sz_equal_k = 0, sz_greater_k = 1 } sz_ordering_t; // Only three possible states: <=>
+
+/**
+ *  @brief  Tiny string-view structure. It's POD type, unlike the `std::string_view`.
+ */
+typedef struct sz_string_view_t {
+    sz_cptr_t start;
+    sz_size_t length;
+} sz_string_view_t;
+
+/**
+ *  @brief  Enumeration of SIMD capabilities of the target architecture.
+ *          Used to introspect the supported functionality of the dynamic library.
+ */
+typedef enum sz_capability_t {
+    sz_cap_serial_k = 1,       /// Serial (non-SIMD) capability
+    sz_cap_any_k = 0x7FFFFFFF, /// Mask representing any capability
+
+    sz_cap_arm_neon_k = 1 << 10, /// ARM NEON capability
+    sz_cap_arm_sve_k = 1 << 11,  /// ARM SVE capability TODO: Not yet supported or used
+
+    sz_cap_x86_avx2_k = 1 << 20,       /// x86 AVX2 capability
+    sz_cap_x86_avx512f_k = 1 << 21,    /// x86 AVX512 F capability
+    sz_cap_x86_avx512bw_k = 1 << 22,   /// x86 AVX512 BW instruction capability
+    sz_cap_x86_avx512vl_k = 1 << 23,   /// x86 AVX512 VL instruction capability
+    sz_cap_x86_avx512vbmi_k = 1 << 24, /// x86 AVX512 VBMI instruction capability
+    sz_cap_x86_gfni_k = 1 << 25,       /// x86 AVX512 GFNI instruction capability
+
+} sz_capability_t;
+
+/**
+ *  @brief  Function to determine the SIMD capabilities of the current machine @b only at @b runtime.
+ *  @return A bitmask of the SIMD capabilities represented as a `sz_capability_t` enum value.
+ */
+SZ_DYNAMIC sz_capability_t sz_capabilities(void);
+
+/**
+ *  @brief  Bit-set structure for 256 possible byte values. Useful for filtering and search.
+ *  @see    sz_charset_init, sz_charset_add, sz_charset_contains, sz_charset_invert
+ */
+typedef union sz_charset_t {
+    sz_u64_t _u64s[4];
+    sz_u32_t _u32s[8];
+    sz_u16_t _u16s[16];
+    sz_u8_t _u8s[32];
+} sz_charset_t;
+
+/** @brief  Initializes a bit-set to an empty collection, meaning - all characters are banned. */
+SZ_PUBLIC void sz_charset_init(sz_charset_t *s) { s->_u64s[0] = s->_u64s[1] = s->_u64s[2] = s->_u64s[3] = 0; }
+
+/** @brief  Adds a character to the set and accepts @b unsigned integers. */
+SZ_PUBLIC void sz_charset_add_u8(sz_charset_t *s, sz_u8_t c) { s->_u64s[c >> 6] |= (1ull << (c & 63u)); }
+
+/** @brief  Adds a character to the set. Consider @b sz_charset_add_u8. */
+SZ_PUBLIC void sz_charset_add(sz_charset_t *s, char c) { sz_charset_add_u8(s, *(sz_u8_t *)(&c)); } // bitcast
+
+/** @brief  Checks if the set contains a given character and accepts @b unsigned integers. */
+SZ_PUBLIC sz_bool_t sz_charset_contains_u8(sz_charset_t const *s, sz_u8_t c) {
+    // Checking the bit can be done in different ways:
+    // - (s->_u64s[c >> 6] & (1ull << (c & 63u))) != 0
+    // - (s->_u32s[c >> 5] & (1u << (c & 31u))) != 0
+    // - (s->_u16s[c >> 4] & (1u << (c & 15u))) != 0
+    // - (s->_u8s[c >> 3] & (1u << (c & 7u))) != 0
+    return (sz_bool_t)((s->_u64s[c >> 6] & (1ull << (c & 63u))) != 0);
+}
+
+/** @brief  Checks if the set contains a given character. Consider @b sz_charset_contains_u8. */
+SZ_PUBLIC sz_bool_t sz_charset_contains(sz_charset_t const *s, char c) {
+    return sz_charset_contains_u8(s, *(sz_u8_t *)(&c)); // bitcast
+}
+
+/** @brief  Inverts the contents of the set, so allowed character get disallowed, and vice versa. */
+SZ_PUBLIC void sz_charset_invert(sz_charset_t *s) {
+    s->_u64s[0] ^= 0xFFFFFFFFFFFFFFFFull, s->_u64s[1] ^= 0xFFFFFFFFFFFFFFFFull, //
+        s->_u64s[2] ^= 0xFFFFFFFFFFFFFFFFull, s->_u64s[3] ^= 0xFFFFFFFFFFFFFFFFull;
+}
+
+typedef void *(*sz_memory_allocate_t)(sz_size_t, void *);
+typedef void (*sz_memory_free_t)(void *, sz_size_t, void *);
+typedef sz_u64_t (*sz_random_generator_t)(void *);
+
+/**
+ *  @brief  Some complex pattern matching algorithms may require memory allocations.
+ *          This structure is used to pass the memory allocator to those functions.
+ *  @see    sz_memory_allocator_init_fixed
+ */
+typedef struct sz_memory_allocator_t {
+    sz_memory_allocate_t allocate;
+    sz_memory_free_t free;
+    void *handle;
+} sz_memory_allocator_t;
+
+/**
+ *  @brief  Initializes a memory allocator to use the system default `malloc` and `free`.
+ *  @param alloc    Memory allocator to initialize.
+ */
+SZ_PUBLIC void sz_memory_allocator_init_default(sz_memory_allocator_t *alloc);
+
+/**
+ *  @brief  Initializes a memory allocator to use a static-capacity buffer.
+ *          No dynamic allocations will be performed.
+ *
+ *  @param alloc    Memory allocator to initialize.
+ *  @param buffer   Buffer to use for allocations.
+ *  @param length   Length of the buffer. @b Must be greater than 8 bytes. Different values would be optimal for
+ *                  different algorithms and input lengths, but 4096 bytes (one RAM page) is a good default.
+ */
+SZ_PUBLIC void sz_memory_allocator_init_fixed(sz_memory_allocator_t *alloc, void *buffer, sz_size_t length);
+
+/**
+ *  @brief  The number of bytes a stack-allocated string can hold, including the SZ_NULL termination character.
+ *          ! This can't be changed from outside. Don't use the `#error` as it may already be included and set.
+ */
+#ifdef SZ_STRING_INTERNAL_SPACE
+#undef SZ_STRING_INTERNAL_SPACE
+#endif
+#define SZ_STRING_INTERNAL_SPACE (23)
+
+/**
+ *  @brief  Tiny memory-owning string structure with a Small String Optimization (SSO).
+ *          Differs in layout from Folly, Clang, GCC, and probably most other implementations.
+ *          It's designed to avoid any branches on read-only operations, and can store up
+ *          to 22 characters on stack, followed by the SZ_NULL-termination character.
+ *
+ *  @section Changing Length
+ *
+ *  One nice thing about this design, is that you can, in many cases, change the length of the string
+ *  without any branches, invoking a `+=` or `-=` on the 64-bit `length` field. If the string is on heap,
+ *  the solution is obvious. If it's on stack, inplace decrement wouldn't affect the top bytes of the string,
+ *  only changing the last byte containing the length.
+ */
+typedef union sz_string_t {
+
+    struct internal {
+        sz_ptr_t start;
+        sz_u8_t length;
+        char chars[SZ_STRING_INTERNAL_SPACE];
+    } internal;
+
+    struct external {
+        sz_ptr_t start;
+        sz_size_t length;
+        /// @brief Number of bytes, that have been allocated for this string, equals to (capacity + 1).
+        sz_size_t space;
+        sz_size_t padding;
+    } external;
+
+    sz_u64_t u64s[4];
+
+} sz_string_t;
+
+typedef sz_u64_t (*sz_hash_t)(sz_cptr_t, sz_size_t);
+typedef sz_bool_t (*sz_equal_t)(sz_cptr_t, sz_cptr_t, sz_size_t);
+typedef sz_ordering_t (*sz_order_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t);
+typedef void (*sz_to_converter_t)(sz_cptr_t, sz_size_t, sz_ptr_t);
+
+/**
+ *  @brief  Computes the 64-bit unsigned hash of a string. Fairly fast for short strings,
+ *          simple implementation, and supports rolling computation, reused in other APIs.
+ *          Similar to `std::hash` in C++.
+ *
+ *  @param text     String to hash.
+ *  @param length   Number of bytes in the text.
+ *  @return         64-bit hash value.
+ *
+ *  @see    sz_hashes, sz_hashes_fingerprint, sz_hashes_intersection
+ */
+SZ_PUBLIC sz_u64_t sz_hash(sz_cptr_t text, sz_size_t length);
+
+/** @copydoc sz_hash */
+SZ_PUBLIC sz_u64_t sz_hash_serial(sz_cptr_t text, sz_size_t length);
+
+/**
+ *  @brief  Checks if two string are equal.
+ *          Similar to `memcmp(a, b, length) == 0` in LibC and `a == b` in STL.
+ *
+ *  The implementation of this function is very similar to `sz_order`, but the usage patterns are different.
+ *  This function is more often used in parsing, while `sz_order` is often used in sorting.
+ *  It works best on platforms with cheap
+ *
+ *  @param a        First string to compare.
+ *  @param b        Second string to compare.
+ *  @param length   Number of bytes in both strings.
+ *  @return         1 if strings match, 0 otherwise.
+ */
+SZ_DYNAMIC sz_bool_t sz_equal(sz_cptr_t a, sz_cptr_t b, sz_size_t length);
+
+/** @copydoc sz_equal */
+SZ_PUBLIC sz_bool_t sz_equal_serial(sz_cptr_t a, sz_cptr_t b, sz_size_t length);
+
+/**
+ *  @brief  Estimates the relative order of two strings. Equivalent to `memcmp(a, b, length)` in LibC.
+ *          Can be used on different length strings.
+ *
+ *  @param a        First string to compare.
+ *  @param a_length Number of bytes in the first string.
+ *  @param b        Second string to compare.
+ *  @param b_length Number of bytes in the second string.
+ *  @return         Negative if (a < b), positive if (a > b), zero if they are equal.
+ */
+SZ_DYNAMIC sz_ordering_t sz_order(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length);
+
+/** @copydoc sz_order */
+SZ_PUBLIC sz_ordering_t sz_order_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length);
+
+/**
+ *  @brief  Equivalent to `for (char & c : text) c = tolower(c)`.
+ *
+ *  ASCII characters [A, Z] map to decimals [65, 90], and [a, z] map to [97, 122].
+ *  So there are 26 english letters, shifted by 32 values, meaning that a conversion
+ *  can be done by flipping the 5th bit each inappropriate character byte. This, however,
+ *  breaks for extended ASCII, so a different solution is needed.
+ *  http://0x80.pl/notesen/2016-01-06-swar-swap-case.html
+ *
+ *  @param text     String to be normalized.
+ *  @param length   Number of bytes in the string.
+ *  @param result   Output string, can point to the same address as ::text.
+ */
+SZ_PUBLIC void sz_tolower(sz_cptr_t text, sz_size_t length, sz_ptr_t result);
+
+/**
+ *  @brief  Equivalent to `for (char & c : text) c = toupper(c)`.
+ *
+ *  ASCII characters [A, Z] map to decimals [65, 90], and [a, z] map to [97, 122].
+ *  So there are 26 english letters, shifted by 32 values, meaning that a conversion
+ *  can be done by flipping the 5th bit each inappropriate character byte. This, however,
+ *  breaks for extended ASCII, so a different solution is needed.
+ *  http://0x80.pl/notesen/2016-01-06-swar-swap-case.html
+ *
+ *  @param text     String to be normalized.
+ *  @param length   Number of bytes in the string.
+ *  @param result   Output string, can point to the same address as ::text.
+ */
+SZ_PUBLIC void sz_toupper(sz_cptr_t text, sz_size_t length, sz_ptr_t result);
+
+/**
+ *  @brief  Equivalent to `for (char & c : text) c = toascii(c)`.
+ *
+ *  @param text     String to be normalized.
+ *  @param length   Number of bytes in the string.
+ *  @param result   Output string, can point to the same address as ::text.
+ */
+SZ_PUBLIC void sz_toascii(sz_cptr_t text, sz_size_t length, sz_ptr_t result);
+
+/**
+ *  @brief  Generates a random string for a given alphabet, avoiding integer division and modulo operations.
+ *          Similar to `text[i] = alphabet[rand() % cardinality]`.
+ *
+ *  The modulo operation is expensive, and should be avoided in performance-critical code.
+ *  We avoid it using small lookup tables and replacing it with a multiplication and shifts, similar to `libdivide`.
+ *  Alternative algorithms would include:
+ *      - Montgomery form: https://en.algorithmica.org/hpc/number-theory/montgomery/
+ *      - Barret reduction: https://www.nayuki.io/page/barrett-reduction-algorithm
+ *      - Lemire's trick: https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+ *
+ *  @param alphabet     Set of characters to sample from.
+ *  @param cardinality  Number of characters to sample from.
+ *  @param text         Output string, can point to the same address as ::text.
+ *  @param generate     Callback producing random numbers given the generator state.
+ *  @param generator    Generator state, can be a pointer to a seed, or a pointer to a random number generator.
+ */
+SZ_PUBLIC void sz_generate(sz_cptr_t alphabet, sz_size_t cardinality, sz_ptr_t text, sz_size_t length,
+                           sz_random_generator_t generate, void *generator);
+
+/**
+ *  @brief  Similar to `memcpy`, copies contents of one string into another.
+ *          The behavior is undefined if the strings overlap.
+ *
+ *  @param target   String to copy into.
+ *  @param length   Number of bytes to copy.
+ *  @param source   String to copy from.
+ */
+SZ_DYNAMIC void sz_copy(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+
+/** @copydoc sz_copy */
+SZ_PUBLIC void sz_copy_serial(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+
+/**
+ *  @brief  Similar to `memmove`, copies (moves) contents of one string into another.
+ *          Unlike `sz_copy`, allows overlapping strings as arguments.
+ *
+ *  @param target   String to copy into.
+ *  @param length   Number of bytes to copy.
+ *  @param source   String to copy from.
+ */
+SZ_DYNAMIC void sz_move(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+
+/** @copydoc sz_move */
+SZ_PUBLIC void sz_move_serial(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+
+typedef void (*sz_move_t)(sz_ptr_t, sz_cptr_t, sz_size_t);
+
+/**
+ *  @brief  Similar to `memset`, fills a string with a given value.
+ *
+ *  @param target   String to fill.
+ *  @param length   Number of bytes to fill.
+ *  @param value    Value to fill with.
+ */
+SZ_DYNAMIC void sz_fill(sz_ptr_t target, sz_size_t length, sz_u8_t value);
+
+/** @copydoc sz_fill */
+SZ_PUBLIC void sz_fill_serial(sz_ptr_t target, sz_size_t length, sz_u8_t value);
+
+typedef void (*sz_fill_t)(sz_ptr_t, sz_size_t, sz_u8_t);
+
+/**
+ *  @brief  Initializes a string class instance to an empty value.
+ */
+SZ_PUBLIC void sz_string_init(sz_string_t *string);
+
+/**
+ *  @brief  Convenience function checking if the provided string is stored inside of the ::string instance itself,
+ *          alternative being - allocated in a remote region of the heap.
+ */
+SZ_PUBLIC sz_bool_t sz_string_is_on_stack(sz_string_t const *string);
+
+/**
+ *  @brief  Unpacks the opaque instance of a string class into its components.
+ *          Recommended to use only in read-only operations.
+ *
+ *  @param string       String to unpack.
+ *  @param start        Pointer to the start of the string.
+ *  @param length       Number of bytes in the string, before the SZ_NULL character.
+ *  @param space        Number of bytes allocated for the string (heap or stack), including the SZ_NULL character.
+ *  @param is_external  Whether the string is allocated on the heap externally, or fits withing ::string instance.
+ */
+SZ_PUBLIC void sz_string_unpack(sz_string_t const *string, sz_ptr_t *start, sz_size_t *length, sz_size_t *space,
+                                sz_bool_t *is_external);
+
+/**
+ *  @brief  Unpacks only the start and length of the string.
+ *          Recommended to use only in read-only operations.
+ *
+ * @param string       String to unpack.
+ * @param start        Pointer to the start of the string.
+ * @param length       Number of bytes in the string, before the SZ_NULL character.
+ */
+SZ_PUBLIC void sz_string_range(sz_string_t const *string, sz_ptr_t *start, sz_size_t *length);
+
+/**
+ *  @brief  Constructs a string of a given ::length with noisy contents.
+ *          Use the returned character pointer to populate the string.
+ *
+ *  @param string       String to initialize.
+ *  @param length       Number of bytes in the string, before the SZ_NULL character.
+ *  @param allocator    Memory allocator to use for the allocation.
+ *  @return             SZ_NULL if the operation failed, pointer to the start of the string otherwise.
+ */
+SZ_PUBLIC sz_ptr_t sz_string_init_length(sz_string_t *string, sz_size_t length, sz_memory_allocator_t *allocator);
+
+/**
+ *  @brief  Doesn't change the contents or the length of the string, but grows the available memory capacity.
+ *          This is beneficial, if several insertions are expected, and we want to minimize allocations.
+ *
+ *  @param string       String to grow.
+ *  @param new_capacity The number of characters to reserve space for, including existing ones.
+ *  @param allocator    Memory allocator to use for the allocation.
+ *  @return             SZ_NULL if the operation failed, pointer to the new start of the string otherwise.
+ */
+SZ_PUBLIC sz_ptr_t sz_string_reserve(sz_string_t *string, sz_size_t new_capacity, sz_memory_allocator_t *allocator);
+
+/**
+ *  @brief  Grows the string by adding an uninitialized region of ::added_length at the given ::offset.
+ *          Would often be used in conjunction with one or more `sz_copy` calls to populate the allocated region.
+ *          Similar to `sz_string_reserve`, but changes the length of the ::string.
+ *
+ *  @param string       String to grow.
+ *  @param offset       Offset of the first byte to reserve space for.
+ *                      If provided offset is larger than the length, it will be capped.
+ *  @param added_length The number of new characters to reserve space for.
+ *  @param allocator    Memory allocator to use for the allocation.
+ *  @return             SZ_NULL if the operation failed, pointer to the new start of the string otherwise.
+ */
+SZ_PUBLIC sz_ptr_t sz_string_expand(sz_string_t *string, sz_size_t offset, sz_size_t added_length,
+                                    sz_memory_allocator_t *allocator);
+
+/**
+ *  @brief  Removes a range from a string. Changes the length, but not the capacity.
+ *          Performs no allocations or deallocations and can't fail.
+ *
+ *  @param string       String to clean.
+ *  @param offset       Offset of the first byte to remove.
+ *  @param length       Number of bytes to remove. Out-of-bound ranges will be capped.
+ *  @return             Number of bytes removed.
+ */
+SZ_PUBLIC sz_size_t sz_string_erase(sz_string_t *string, sz_size_t offset, sz_size_t length);
+
+/**
+ *  @brief  Shrinks the string to fit the current length, if it's allocated on the heap.
+ *          Teh reverse operation of ::sz_string_reserve.
+ *
+ *  @param string       String to shrink.
+ *  @param allocator    Memory allocator to use for the allocation.
+ *  @return             Whether the operation was successful. The only failures can come from the allocator.
+ */
+SZ_PUBLIC sz_ptr_t sz_string_shrink_to_fit(sz_string_t *string, sz_memory_allocator_t *allocator);
+
+/**
+ *  @brief  Frees the string, if it's allocated on the heap.
+ *          If the string is on the stack, the function clears/resets the state.
+ */
+SZ_PUBLIC void sz_string_free(sz_string_t *string, sz_memory_allocator_t *allocator);
+
+#pragma endregion
+
+#pragma region Fast Substring Search API
+
+typedef sz_cptr_t (*sz_find_byte_t)(sz_cptr_t, sz_size_t, sz_cptr_t);
+typedef sz_cptr_t (*sz_find_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t);
+typedef sz_cptr_t (*sz_find_set_t)(sz_cptr_t, sz_size_t, sz_charset_t const *);
+
+/**
+ *  @brief  Locates first matching byte in a string. Equivalent to `memchr(haystack, *needle, h_length)` in LibC.
+ *
+ *  X86_64 implementation: https://github.com/lattera/glibc/blob/master/sysdeps/x86_64/memchr.S
+ *  Aarch64 implementation: https://github.com/lattera/glibc/blob/master/sysdeps/aarch64/memchr.S
+ *
+ *  @param haystack Haystack - the string to search in.
+ *  @param h_length Number of bytes in the haystack.
+ *  @param needle   Needle - single-byte substring to find.
+ *  @return         Address of the first match.
+ */
+SZ_DYNAMIC sz_cptr_t sz_find_byte(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+
+/** @copydoc sz_find_byte */
+SZ_PUBLIC sz_cptr_t sz_find_byte_serial(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+
+/**
+ *  @brief  Locates last matching byte in a string. Equivalent to `memrchr(haystack, *needle, h_length)` in LibC.
+ *
+ *  X86_64 implementation: https://github.com/lattera/glibc/blob/master/sysdeps/x86_64/memrchr.S
+ *  Aarch64 implementation: missing
+ *
+ *  @param haystack Haystack - the string to search in.
+ *  @param h_length Number of bytes in the haystack.
+ *  @param needle   Needle - single-byte substring to find.
+ *  @return         Address of the last match.
+ */
+SZ_DYNAMIC sz_cptr_t sz_rfind_byte(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+
+/** @copydoc sz_rfind_byte */
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_serial(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+
+/**
+ *  @brief  Locates first matching substring.
+ *          Equivalent to `memmem(haystack, h_length, needle, n_length)` in LibC.
+ *          Similar to `strstr(haystack, needle)` in LibC, but requires known length.
+ *
+ *  @param haystack Haystack - the string to search in.
+ *  @param h_length Number of bytes in the haystack.
+ *  @param needle   Needle - substring to find.
+ *  @param n_length Number of bytes in the needle.
+ *  @return         Address of the first match.
+ */
+SZ_DYNAMIC sz_cptr_t sz_find(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+
+/** @copydoc sz_find */
+SZ_PUBLIC sz_cptr_t sz_find_serial(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+
+/**
+ *  @brief  Locates the last matching substring.
+ *
+ *  @param haystack Haystack - the string to search in.
+ *  @param h_length Number of bytes in the haystack.
+ *  @param needle   Needle - substring to find.
+ *  @param n_length Number of bytes in the needle.
+ *  @return         Address of the last match.
+ */
+SZ_DYNAMIC sz_cptr_t sz_rfind(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+
+/** @copydoc sz_rfind */
+SZ_PUBLIC sz_cptr_t sz_rfind_serial(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+
+/**
+ *  @brief  Finds the first character present from the ::set, present in ::text.
+ *          Equivalent to `strspn(text, accepted)` and `strcspn(text, rejected)` in LibC.
+ *          May have identical implementation and performance to ::sz_rfind_charset.
+ *
+ *  @param text     String to be trimmed.
+ *  @param accepted Set of accepted characters.
+ *  @return         Number of bytes forming the prefix.
+ */
+SZ_DYNAMIC sz_cptr_t sz_find_charset(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+
+/** @copydoc sz_find_charset */
+SZ_PUBLIC sz_cptr_t sz_find_charset_serial(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+
+/**
+ *  @brief  Finds the last character present from the ::set, present in ::text.
+ *          Equivalent to `strspn(text, accepted)` and `strcspn(text, rejected)` in LibC.
+ *          May have identical implementation and performance to ::sz_find_charset.
+ *
+ *  Useful for parsing, when we want to skip a set of characters. Examples:
+ *  * 6 whitespaces: " \t\n\r\v\f".
+ *  * 16 digits forming a float number: "0123456789,.eE+-".
+ *  * 5 HTML reserved characters: "\"'&<>", of which "<>" can be useful for parsing.
+ *  * 2 JSON string special characters useful to locate the end of the string: "\"\\".
+ *
+ *  @param text     String to be trimmed.
+ *  @param rejected Set of rejected characters.
+ *  @return         Number of bytes forming the prefix.
+ */
+SZ_DYNAMIC sz_cptr_t sz_rfind_charset(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+
+/** @copydoc sz_rfind_charset */
+SZ_PUBLIC sz_cptr_t sz_rfind_charset_serial(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+
+#pragma endregion
+
+#pragma region String Similarity Measures API
+
+/**
+ *  @brief  Computes the Levenshtein edit-distance between two strings using the Wagner-Fisher algorithm.
+ *          Similar to the Needleman-Wunsch alignment algorithm. Often used in fuzzy string matching.
+ *
+ *  @param a        First string to compare.
+ *  @param a_length Number of bytes in the first string.
+ *  @param b        Second string to compare.
+ *  @param b_length Number of bytes in the second string.
+ *
+ *  @param alloc    Temporary memory allocator. Only some of the rows of the matrix will be allocated,
+ *                  so the memory usage is linear in relation to ::a_length and ::b_length.
+ *                  If SZ_NULL is passed, will initialize to the systems default `malloc`.
+ *  @param bound    Upper bound on the distance, that allows us to exit early.
+ *                  If zero is passed, the maximum possible distance will be equal to the length of the longer input.
+ *  @return         Unsigned integer for edit distance, the `bound` if was exceeded or `SZ_SIZE_MAX`
+ *                  if the memory allocation failed.
+ *
+ *  @see    sz_memory_allocator_init_fixed, sz_memory_allocator_init_default
+ *  @see    https://en.wikipedia.org/wiki/Levenshtein_distance
+ */
+SZ_DYNAMIC sz_size_t sz_edit_distance(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
+                                      sz_size_t bound, sz_memory_allocator_t *alloc);
+
+/** @copydoc sz_edit_distance */
+SZ_PUBLIC sz_size_t sz_edit_distance_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
+                                            sz_size_t bound, sz_memory_allocator_t *alloc);
+
+typedef sz_size_t (*sz_edit_distance_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t, sz_size_t, sz_memory_allocator_t *);
+
+/**
+ *  @brief  Computes Needleman–Wunsch alignment score for two string. Often used in bioinformatics and cheminformatics.
+ *          Similar to the Levenshtein edit-distance, parameterized for gap and substitution penalties.
+ *
+ *  Not commutative in the general case, as the order of the strings matters, as `sz_alignment_score(a, b)` may
+ *  not be equal to `sz_alignment_score(b, a)`. Becomes @b commutative, if the substitution costs are symmetric.
+ *  Equivalent to the negative Levenshtein distance, if: `gap == -1` and `subs[i][j] == (i == j ? 0: -1)`.
+ *
+ *  @param a        First string to compare.
+ *  @param a_length Number of bytes in the first string.
+ *  @param b        Second string to compare.
+ *  @param b_length Number of bytes in the second string.
+ *  @param gap      Penalty cost for gaps - insertions and removals.
+ *  @param subs     Substitution costs matrix with 256 x 256 values for all pairs of characters.
+ *
+ *  @param alloc    Temporary memory allocator. Only some of the rows of the matrix will be allocated,
+ *                  so the memory usage is linear in relation to ::a_length and ::b_length.
+ *                  If SZ_NULL is passed, will initialize to the systems default `malloc`.
+ *  @return         Signed similarity score. Can be negative, depending on the substitution costs.
+ *                  If the memory allocation fails, the function returns `SZ_SSIZE_MAX`.
+ *
+ *  @see    sz_memory_allocator_init_fixed, sz_memory_allocator_init_default
+ *  @see    https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm
+ */
+SZ_DYNAMIC sz_ssize_t sz_alignment_score(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
+                                         sz_error_cost_t const *subs, sz_error_cost_t gap,                 //
+                                         sz_memory_allocator_t *alloc);
+
+/** @copydoc sz_alignment_score */
+SZ_PUBLIC sz_ssize_t sz_alignment_score_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
+                                               sz_error_cost_t const *subs, sz_error_cost_t gap,                 //
+                                               sz_memory_allocator_t *alloc);
+
+typedef sz_ssize_t (*sz_alignment_score_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t, sz_error_cost_t const *,
+                                           sz_error_cost_t, sz_memory_allocator_t *);
+
+typedef void (*sz_hash_callback_t)(sz_cptr_t, sz_size_t, sz_u64_t, void *user);
+
+/**
+ *  @brief  Computes the Karp-Rabin rolling hashes of a string supplying them to the provided `callback`.
+ *          Can be used for similarity scores, search, ranking, etc.
+ *
+ *  Rabin-Karp-like rolling hashes can have very high-level of collisions and depend
+ *  on the choice of bases and the prime number. That's why, often two hashes from the same
+ *  family are used with different bases.
+ *
+ *       1. Kernighan and Ritchie's function uses 31, a prime close to the size of English alphabet.
+ *       2. To be friendlier to byte-arrays and UTF8, we use 257 for the second function.
+ *
+ *  Choosing the right ::window_length is task- and domain-dependant. For example, most English words are
+ *  between 3 and 7 characters long, so a window of 4 bytes would be a good choice. For DNA sequences,
+ *  the ::window_length might be a multiple of 3, as the codons are 3 (nucleotides) bytes long.
+ *  With such minimalistic alphabets of just four characters (AGCT) longer windows might be needed.
+ *  For protein sequences the alphabet is 20 characters long, so the window can be shorter, than for DNAs.
+ *
+ *  @param text             String to hash.
+ *  @param length           Number of bytes in the string.
+ *  @param window_length    Length of the rolling window in bytes.
+ *  @param window_step      Step of reported hashes. @b Must be power of two. Should be smaller than `window_length`.
+ *  @param callback         Function receiving the start & length of a substring, the hash, and the `callback_handle`.
+ *  @param callback_handle  Optional user-provided pointer to be passed to the `callback`.
+ *  @see                    sz_hashes_fingerprint, sz_hashes_intersection
+ */
+SZ_DYNAMIC void sz_hashes(sz_cptr_t text, sz_size_t length, sz_size_t window_length, sz_size_t window_step, //
+                          sz_hash_callback_t callback, void *callback_handle);
+
+/** @copydoc sz_hashes */
+SZ_PUBLIC void sz_hashes_serial(sz_cptr_t text, sz_size_t length, sz_size_t window_length, sz_size_t window_step, //
+                                sz_hash_callback_t callback, void *callback_handle);
+
+typedef void (*sz_hashes_t)(sz_cptr_t, sz_size_t, sz_size_t, sz_size_t, sz_hash_callback_t, void *);
+
+/**
+ *  @brief  Computes the Karp-Rabin rolling hashes of a string outputting a binary fingerprint.
+ *          Such fingerprints can be compared with Hamming or Jaccard (Tanimoto) distance for similarity.
+ *
+ *  The algorithm doesn't clear the fingerprint buffer on start, so it can be invoked multiple times
+ *  to produce a fingerprint of a longer string, by passing the previous fingerprint as the ::fingerprint.
+ *  It can also be reused to produce multi-resolution fingerprints by changing the ::window_length
+ *  and calling the same function multiple times for the same input ::text.
+ *
+ *  Processes large strings in parts to maximize the cache utilization, using a small on-stack buffer,
+ *  avoiding cache-coherency penalties of remote on-heap buffers.
+ *
+ *  @param text                 String to hash.
+ *  @param length               Number of bytes in the string.
+ *  @param fingerprint          Output fingerprint buffer.
+ *  @param fingerprint_bytes    Number of bytes in the fingerprint buffer.
+ *  @param window_length        Length of the rolling window in bytes.
+ *  @see                        sz_hashes, sz_hashes_intersection
+ */
+SZ_PUBLIC void sz_hashes_fingerprint(sz_cptr_t text, sz_size_t length, sz_size_t window_length, //
+                                     sz_ptr_t fingerprint, sz_size_t fingerprint_bytes);
+
+typedef void (*sz_hashes_fingerprint_t)(sz_cptr_t, sz_size_t, sz_size_t, sz_ptr_t, sz_size_t);
+
+/**
+ *  @brief  Given a hash-fingerprint of a textual document, computes the number of intersecting hashes
+ *          of the incoming document. Can be used for document scoring and search.
+ *
+ *  Processes large strings in parts to maximize the cache utilization, using a small on-stack buffer,
+ *  avoiding cache-coherency penalties of remote on-heap buffers.
+ *
+ *  @param text                 Input document.
+ *  @param length               Number of bytes in the input document.
+ *  @param fingerprint          Reference document fingerprint.
+ *  @param fingerprint_bytes    Number of bytes in the reference documents fingerprint.
+ *  @param window_length        Length of the rolling window in bytes.
+ *  @see                        sz_hashes, sz_hashes_fingerprint
+ */
+SZ_PUBLIC sz_size_t sz_hashes_intersection(sz_cptr_t text, sz_size_t length, sz_size_t window_length, //
+                                           sz_cptr_t fingerprint, sz_size_t fingerprint_bytes);
+
+typedef sz_size_t (*sz_hashes_intersection_t)(sz_cptr_t, sz_size_t, sz_size_t, sz_cptr_t, sz_size_t);
+
+#pragma endregion
+
+#pragma region Convenience API
+
+/**
+ *  @brief  Finds the first character in the haystack, that is present in the needle.
+ *          Convenience function, reused across different language bindings.
+ *  @see    sz_find_charset
+ */
+SZ_DYNAMIC sz_cptr_t sz_find_char_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length);
+
+/**
+ *  @brief  Finds the first character in the haystack, that is @b not present in the needle.
+ *          Convenience function, reused across different language bindings.
+ *  @see    sz_find_charset
+ */
+SZ_DYNAMIC sz_cptr_t sz_find_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length);
+
+/**
+ *  @brief  Finds the last character in the haystack, that is present in the needle.
+ *          Convenience function, reused across different language bindings.
+ *  @see    sz_find_charset
+ */
+SZ_DYNAMIC sz_cptr_t sz_rfind_char_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length);
+
+/**
+ *  @brief  Finds the last character in the haystack, that is @b not present in the needle.
+ *          Convenience function, reused across different language bindings.
+ *  @see    sz_find_charset
+ */
+SZ_DYNAMIC sz_cptr_t sz_rfind_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length);
+
+#pragma endregion
+
+#pragma region String Sequences API
+
+struct sz_sequence_t;
+
+typedef sz_cptr_t (*sz_sequence_member_start_t)(struct sz_sequence_t const *, sz_size_t);
+typedef sz_size_t (*sz_sequence_member_length_t)(struct sz_sequence_t const *, sz_size_t);
+typedef sz_bool_t (*sz_sequence_predicate_t)(struct sz_sequence_t const *, sz_size_t);
+typedef sz_bool_t (*sz_sequence_comparator_t)(struct sz_sequence_t const *, sz_size_t, sz_size_t);
+typedef sz_bool_t (*sz_string_is_less_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t);
+
+typedef struct sz_sequence_t {
+    sz_u64_t *order;
+    sz_size_t count;
+    sz_sequence_member_start_t get_start;
+    sz_sequence_member_length_t get_length;
+    void const *handle;
+} sz_sequence_t;
+
+/**
+ *  @brief  Initiates the sequence structure from a tape layout, used by Apache Arrow.
+ *          Expects ::offsets to contains `count + 1` entries, the last pointing at the end
+ *          of the last string, indicating the total length of the ::tape.
+ */
+SZ_PUBLIC void sz_sequence_from_u32tape(sz_cptr_t *start, sz_u32_t const *offsets, sz_size_t count,
+                                        sz_sequence_t *sequence);
+
+/**
+ *  @brief  Initiates the sequence structure from a tape layout, used by Apache Arrow.
+ *          Expects ::offsets to contains `count + 1` entries, the last pointing at the end
+ *          of the last string, indicating the total length of the ::tape.
+ */
+SZ_PUBLIC void sz_sequence_from_u64tape(sz_cptr_t *start, sz_u64_t const *offsets, sz_size_t count,
+                                        sz_sequence_t *sequence);
+
+/**
+ *  @brief  Similar to `std::partition`, given a predicate splits the sequence into two parts.
+ *          The algorithm is unstable, meaning that elements may change relative order, as long
+ *          as they are in the right partition. This is the simpler algorithm for partitioning.
+ */
+SZ_PUBLIC sz_size_t sz_partition(sz_sequence_t *sequence, sz_sequence_predicate_t predicate);
+
+/**
+ *  @brief  Inplace `std::set_union` for two consecutive chunks forming the same continuous `sequence`.
+ *
+ *  @param partition The number of elements in the first sub-sequence in `sequence`.
+ *  @param less Comparison function, to determine the lexicographic ordering.
+ */
+SZ_PUBLIC void sz_merge(sz_sequence_t *sequence, sz_size_t partition, sz_sequence_comparator_t less);
+
+/**
+ *  @brief  Sorting algorithm, combining Radix Sort for the first 32 bits of every word
+ *          and a follow-up by a more conventional sorting procedure on equally prefixed parts.
+ */
+SZ_PUBLIC void sz_sort(sz_sequence_t *sequence);
+
+/**
+ *  @brief  Partial sorting algorithm, combining Radix Sort for the first 32 bits of every word
+ *          and a follow-up by a more conventional sorting procedure on equally prefixed parts.
+ */
+SZ_PUBLIC void sz_sort_partial(sz_sequence_t *sequence, sz_size_t n);
+
+/**
+ *  @brief  Intro-Sort algorithm that supports custom comparators.
+ */
+SZ_PUBLIC void sz_sort_intro(sz_sequence_t *sequence, sz_sequence_comparator_t less);
+
+#pragma endregion
+
+/*
+ *  Hardware feature detection.
+ *  All of those can be controlled by the user.
+ */
+#ifndef SZ_USE_X86_AVX512
+#ifdef __AVX512BW__
+#define SZ_USE_X86_AVX512 1
+#else
+#define SZ_USE_X86_AVX512 0
+#endif
+#endif
+
+#ifndef SZ_USE_X86_AVX2
+#ifdef __AVX2__
+#define SZ_USE_X86_AVX2 1
+#else
+#define SZ_USE_X86_AVX2 0
+#endif
+#endif
+
+#ifndef SZ_USE_ARM_NEON
+#ifdef __ARM_NEON
+#define SZ_USE_ARM_NEON 1
+#else
+#define SZ_USE_ARM_NEON 0
+#endif
+#endif
+
+#ifndef SZ_USE_ARM_SVE
+#ifdef __ARM_FEATURE_SVE
+#define SZ_USE_ARM_SVE 1
+#else
+#define SZ_USE_ARM_SVE 0
+#endif
+#endif
+
+/*
+ *  Include hardware-specific headers.
+ */
+#if SZ_USE_X86_AVX512 || SZ_USE_X86_AVX2
+#include <immintrin.h>
+#endif // SZ_USE_X86...
+#if SZ_USE_ARM_NEON
+#include <arm_acle.h>
+#include <arm_neon.h>
+#endif // SZ_USE_ARM_NEON
+#if SZ_USE_ARM_SVE
+#include <arm_sve.h>
+#endif // SZ_USE_ARM_SVE
+
+#pragma region Hardware-Specific API
+
+#if SZ_USE_X86_AVX512
+
+/** @copydoc sz_equal_serial */
+SZ_PUBLIC sz_bool_t sz_equal_avx512(sz_cptr_t a, sz_cptr_t b, sz_size_t length);
+/** @copydoc sz_order_serial */
+SZ_PUBLIC sz_ordering_t sz_order_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length);
+/** @copydoc sz_copy_serial */
+SZ_PUBLIC void sz_copy_avx512(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+/** @copydoc sz_move_serial */
+SZ_PUBLIC void sz_move_avx512(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+/** @copydoc sz_fill_serial */
+SZ_PUBLIC void sz_fill_avx512(sz_ptr_t target, sz_size_t length, sz_u8_t value);
+/** @copydoc sz_find_byte */
+SZ_PUBLIC sz_cptr_t sz_find_byte_avx512(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+/** @copydoc sz_rfind_byte */
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_avx512(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+/** @copydoc sz_find */
+SZ_PUBLIC sz_cptr_t sz_find_avx512(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+/** @copydoc sz_rfind */
+SZ_PUBLIC sz_cptr_t sz_rfind_avx512(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+/** @copydoc sz_find_charset */
+SZ_PUBLIC sz_cptr_t sz_find_charset_avx512(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+/** @copydoc sz_rfind_charset */
+SZ_PUBLIC sz_cptr_t sz_rfind_charset_avx512(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+/** @copydoc sz_edit_distance */
+SZ_PUBLIC sz_size_t sz_edit_distance_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
+                                            sz_size_t bound, sz_memory_allocator_t *alloc);
+/** @copydoc sz_alignment_score */
+SZ_PUBLIC sz_ssize_t sz_alignment_score_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
+                                               sz_error_cost_t const *subs, sz_error_cost_t gap,                 //
+                                               sz_memory_allocator_t *alloc);
+/** @copydoc sz_hashes */
+SZ_PUBLIC void sz_hashes_avx512(sz_cptr_t text, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                                sz_hash_callback_t callback, void *callback_handle);
+#endif
+
+#if SZ_USE_X86_AVX2
+/** @copydoc sz_equal */
+SZ_PUBLIC void sz_copy_avx2(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+/** @copydoc sz_move */
+SZ_PUBLIC void sz_move_avx2(sz_ptr_t target, sz_cptr_t source, sz_size_t length);
+/** @copydoc sz_fill */
+SZ_PUBLIC void sz_fill_avx2(sz_ptr_t target, sz_size_t length, sz_u8_t value);
+/** @copydoc sz_find_byte */
+SZ_PUBLIC sz_cptr_t sz_find_byte_avx2(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+/** @copydoc sz_rfind_byte */
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_avx2(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+/** @copydoc sz_find */
+SZ_PUBLIC sz_cptr_t sz_find_avx2(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+/** @copydoc sz_rfind */
+SZ_PUBLIC sz_cptr_t sz_rfind_avx2(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+/** @copydoc sz_hashes */
+SZ_PUBLIC void sz_hashes_avx2(sz_cptr_t text, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                              sz_hash_callback_t callback, void *callback_handle);
+#endif
+
+#if SZ_USE_ARM_NEON
+/** @copydoc sz_equal */
+SZ_PUBLIC sz_bool_t sz_equal_neon(sz_cptr_t a, sz_cptr_t b, sz_size_t length);
+/** @copydoc sz_find_byte */
+SZ_PUBLIC sz_cptr_t sz_find_byte_neon(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+/** @copydoc sz_rfind_byte */
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_neon(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle);
+/** @copydoc sz_find */
+SZ_PUBLIC sz_cptr_t sz_find_neon(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+/** @copydoc sz_rfind */
+SZ_PUBLIC sz_cptr_t sz_rfind_neon(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length);
+/** @copydoc sz_find_charset */
+SZ_PUBLIC sz_cptr_t sz_find_charset_neon(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+/** @copydoc sz_rfind_charset */
+SZ_PUBLIC sz_cptr_t sz_rfind_charset_neon(sz_cptr_t text, sz_size_t length, sz_charset_t const *set);
+#endif
+
+#pragma endregion
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+
+/*
+ **********************************************************************************************************************
+ **********************************************************************************************************************
+ **********************************************************************************************************************
+ *
+ *  This is where we the actual implementation begins.
+ *  The rest of the file is hidden from the public API.
+ *
+ **********************************************************************************************************************
+ **********************************************************************************************************************
+ **********************************************************************************************************************
+ */
+
+#pragma region Compiler Extensions and Helper Functions
+
+#pragma GCC visibility push(hidden)
+
+/**
+ *  @brief  Helper-macro to mark potentially unused variables.
+ */
+#define sz_unused(x) ((void)(x))
+
+/**
+ *  @brief  Helper-macro casting a variable to another type of the same size.
+ */
+#define sz_bitcast(type, value) (*((type *)&(value)))
+
+/**
+ *  @brief  Defines `SZ_NULL`, analogous to `NULL`.
+ *          The default often comes from locale.h, stddef.h,
+ *          stdio.h, stdlib.h, string.h, time.h, or wchar.h.
+ */
+#ifdef __GNUG__
+#define SZ_NULL __null
+#else
+#define SZ_NULL ((void *)0)
+#endif
+
+/**
+ *  @brief  Cache-line width, that will affect the execution of some algorithms,
+ *          like equality checks and relative order computing.
+ */
+#define SZ_CACHE_LINE_WIDTH (64) // bytes
+
+/**
+ *  @brief  Similar to `assert`, the `sz_assert` is used in the SZ_DEBUG mode
+ *          to check the invariants of the library. It's a no-op in the SZ_RELEASE mode.
+ *  @note   If you want to catch it, put a breakpoint at @b `__GI_exit`
+ */
+#if SZ_DEBUG
+#include <stdio.h>  // `fprintf`
+#include <stdlib.h> // `EXIT_FAILURE`
+#define sz_assert(condition)                                                                                \
+    do {                                                                                                    \
+        if (!(condition)) {                                                                                 \
+            fprintf(stderr, "Assertion failed: %s, in file %s, line %d\n", #condition, __FILE__, __LINE__); \
+            exit(EXIT_FAILURE);                                                                             \
+        }                                                                                                   \
+    } while (0)
+#else
+#define sz_assert(condition) ((void)0)
+#endif
+
+/*
+ *  Intrinsics aliases for MSVC, GCC, and Clang.
+ */
+#if defined(_MSC_VER)
+#include <intrin.h>
+SZ_INTERNAL sz_size_t sz_u64_popcount(sz_u64_t x) { return __popcnt64(x); }
+SZ_INTERNAL sz_size_t sz_u64_ctz(sz_u64_t x) { return _tzcnt_u64(x); }
+SZ_INTERNAL sz_size_t sz_u64_clz(sz_u64_t x) { return _lzcnt_u64(x); }
+SZ_INTERNAL sz_u64_t sz_u64_bytes_reverse(sz_u64_t val) { return _byteswap_uint64(val); }
+SZ_INTERNAL int sz_u32_popcount(sz_u32_t x) { return __popcnt(x); }
+SZ_INTERNAL int sz_u32_ctz(sz_u32_t x) { return _tzcnt_u32(x); }
+SZ_INTERNAL int sz_u32_clz(sz_u32_t x) { return _lzcnt_u32(x); }
+SZ_INTERNAL sz_u32_t sz_u32_bytes_reverse(sz_u32_t val) { return _byteswap_ulong(val); }
+#else
+SZ_INTERNAL int sz_u64_popcount(sz_u64_t x) { return __builtin_popcountll(x); }
+SZ_INTERNAL int sz_u64_ctz(sz_u64_t x) { return __builtin_ctzll(x); }
+SZ_INTERNAL int sz_u64_clz(sz_u64_t x) { return __builtin_clzll(x); }
+SZ_INTERNAL sz_u64_t sz_u64_bytes_reverse(sz_u64_t val) { return __builtin_bswap64(val); }
+SZ_INTERNAL int sz_u32_popcount(sz_u32_t x) { return __builtin_popcount(x); }
+SZ_INTERNAL int sz_u32_ctz(sz_u32_t x) { return __builtin_ctz(x); } // ! Undefined if `x == 0`
+SZ_INTERNAL int sz_u32_clz(sz_u32_t x) { return __builtin_clz(x); } // ! Undefined if `x == 0`
+SZ_INTERNAL sz_u32_t sz_u32_bytes_reverse(sz_u32_t val) { return __builtin_bswap32(val); }
+#endif
+
+SZ_INTERNAL sz_u64_t sz_u64_rotl(sz_u64_t x, sz_u64_t r) { return (x << r) | (x >> (64 - r)); }
+
+/**
+ *  @brief  Select bits from either ::a or ::b depending on the value of ::mask bits.
+ *
+ *  Similar to `_mm_blend_epi16` intrinsic on x86.
+ *  Described in the "Bit Twiddling Hacks" by Sean Eron Anderson.
+ *  https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching
+ */
+SZ_INTERNAL sz_u64_t sz_u64_blend(sz_u64_t a, sz_u64_t b, sz_u64_t mask) { return a ^ ((a ^ b) & mask); }
+
+/*
+ *  Efficiently computing the minimum and maximum of two or three values can be tricky.
+ *  The simple branching baseline would be:
+ *
+ *      x < y ? x : y                               // can replace with 1 conditional move
+ *
+ *  Branchless approach is well known for signed integers, but it doesn't apply to unsigned ones.
+ *  https://stackoverflow.com/questions/514435/templatized-branchless-int-max-min-function
+ *  https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
+ *  Using only bit-shifts for singed integers it would be:
+ *
+ *      y + ((x - y) & (x - y) >> 31)               // 4 unique operations
+ *
+ *  Alternatively, for any integers using multiplication:
+ *
+ *      (x > y) * y + (x <= y) * x                  // 5 operations
+ *
+ *  Alternatively, to avoid multiplication:
+ *
+ *      x & ~((x < y) - 1) + y & ((x < y) - 1)      // 6 unique operations
+ */
+#define sz_min_of_two(x, y) (x < y ? x : y)
+#define sz_max_of_two(x, y) (x < y ? y : x)
+#define sz_min_of_three(x, y, z) sz_min_of_two(x, sz_min_of_two(y, z))
+#define sz_max_of_three(x, y, z) sz_max_of_two(x, sz_max_of_two(y, z))
+
+/** @brief  Branchless minimum function for two signed 32-bit integers. */
+SZ_INTERNAL sz_i32_t sz_i32_min_of_two(sz_i32_t x, sz_i32_t y) { return y + ((x - y) & (x - y) >> 31); }
+
+/** @brief  Branchless minimum function for two signed 32-bit integers. */
+SZ_INTERNAL sz_i32_t sz_i32_max_of_two(sz_i32_t x, sz_i32_t y) { return x - ((x - y) & (x - y) >> 31); }
+
+/**
+ *  @brief  Clamps signed offsets in a string to a valid range. Used for Pythonic-style slicing.
+ */
+SZ_INTERNAL void sz_ssize_clamp_interval(sz_size_t length, sz_ssize_t start, sz_ssize_t end,
+                                         sz_size_t *normalized_offset, sz_size_t *normalized_length) {
+    // TODO: Remove branches.
+    // Normalize negative indices
+    if (start < 0) start += length;
+    if (end < 0) end += length;
+
+    // Clamp indices to a valid range
+    if (start < 0) start = 0;
+    if (end < 0) end = 0;
+    if (start > (sz_ssize_t)length) start = length;
+    if (end > (sz_ssize_t)length) end = length;
+
+    // Ensure start <= end
+    if (start > end) start = end;
+
+    *normalized_offset = start;
+    *normalized_length = end - start;
+}
+
+/**
+ *  @brief  Compute the logarithm base 2 of a positive integer, rounding down.
+ */
+SZ_INTERNAL sz_size_t sz_size_log2i_nonzero(sz_size_t x) {
+    sz_assert(x > 0 && "Non-positive numbers have no defined logarithm");
+    sz_size_t leading_zeros = sz_u64_clz(x);
+    return 63 - leading_zeros;
+}
+
+/**
+ *  @brief  Compute the smallest power of two greater than or equal to ::x.
+ */
+SZ_INTERNAL sz_size_t sz_size_bit_ceil(sz_size_t x) {
+    // Unlike the commonly used trick with `clz` intrinsics, is valid across the whole range of `x`.
+    // https://stackoverflow.com/a/10143264
+    x--;
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    x |= x >> 32;
+    x++;
+    return x;
+}
+
+/**
+ *  @brief  Transposes an 8x8 bit matrix packed in a `sz_u64_t`.
+ *
+ *  There is a well known SWAR sequence for that known to chess programmers,
+ *  willing to flip a bit-matrix of pieces along the main A1-H8 diagonal.
+ *  https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating
+ *  https://lukas-prokop.at/articles/2021-07-23-transpose
+ */
+SZ_INTERNAL sz_u64_t sz_u64_transpose(sz_u64_t x) {
+    sz_u64_t t;
+    t = x ^ (x << 36);
+    x ^= 0xf0f0f0f00f0f0f0full & (t ^ (x >> 36));
+    t = 0xcccc0000cccc0000ull & (x ^ (x << 18));
+    x ^= t ^ (t >> 18);
+    t = 0xaa00aa00aa00aa00ull & (x ^ (x << 9));
+    x ^= t ^ (t >> 9);
+    return x;
+}
+
+/**
+ *  @brief  Helper, that swaps two 64-bit integers representing the order of elements in the sequence.
+ */
+SZ_INTERNAL void sz_u64_swap(sz_u64_t *a, sz_u64_t *b) {
+    sz_u64_t t = *a;
+    *a = *b;
+    *b = t;
+}
+
+/**
+ *  @brief  Helper structure to simplify work with 16-bit words.
+ *  @see    sz_u16_load
+ */
+typedef union sz_u16_vec_t {
+    sz_u16_t u16;
+    sz_u8_t u8s[2];
+} sz_u16_vec_t;
+
+/**
+ *  @brief Load a 16-bit unsigned integer from a potentially unaligned pointer, can be expensive on some platforms.
+ */
+SZ_INTERNAL sz_u16_vec_t sz_u16_load(sz_cptr_t ptr) {
+#if !SZ_USE_MISALIGNED_LOADS
+    sz_u16_vec_t result;
+    result.u8s[0] = ptr[0];
+    result.u8s[1] = ptr[1];
+    return result;
+#elif defined(_MSC_VER)
+    return *((__unaligned sz_u16_vec_t *)ptr);
+#else
+    __attribute__((aligned(1))) sz_u16_vec_t const *result = (sz_u16_vec_t const *)ptr;
+    return *result;
+#endif
+}
+
+/**
+ *  @brief  Helper structure to simplify work with 32-bit words.
+ *  @see    sz_u32_load
+ */
+typedef union sz_u32_vec_t {
+    sz_u32_t u32;
+    sz_u16_t u16s[2];
+    sz_u8_t u8s[4];
+} sz_u32_vec_t;
+
+/**
+ *  @brief Load a 32-bit unsigned integer from a potentially unaligned pointer, can be expensive on some platforms.
+ */
+SZ_INTERNAL sz_u32_vec_t sz_u32_load(sz_cptr_t ptr) {
+#if !SZ_USE_MISALIGNED_LOADS
+    sz_u32_vec_t result;
+    result.u8s[0] = ptr[0];
+    result.u8s[1] = ptr[1];
+    result.u8s[2] = ptr[2];
+    result.u8s[3] = ptr[3];
+    return result;
+#elif defined(_MSC_VER)
+    return *((__unaligned sz_u32_vec_t *)ptr);
+#else
+    __attribute__((aligned(1))) sz_u32_vec_t const *result = (sz_u32_vec_t const *)ptr;
+    return *result;
+#endif
+}
+
+/**
+ *  @brief  Helper structure to simplify work with 64-bit words.
+ *  @see    sz_u64_load
+ */
+typedef union sz_u64_vec_t {
+    sz_u64_t u64;
+    sz_u32_t u32s[2];
+    sz_u16_t u16s[4];
+    sz_u8_t u8s[8];
+} sz_u64_vec_t;
+
+/**
+ *  @brief Load a 64-bit unsigned integer from a potentially unaligned pointer, can be expensive on some platforms.
+ */
+SZ_INTERNAL sz_u64_vec_t sz_u64_load(sz_cptr_t ptr) {
+#if !SZ_USE_MISALIGNED_LOADS
+    sz_u64_vec_t result;
+    result.u8s[0] = ptr[0];
+    result.u8s[1] = ptr[1];
+    result.u8s[2] = ptr[2];
+    result.u8s[3] = ptr[3];
+    result.u8s[4] = ptr[4];
+    result.u8s[5] = ptr[5];
+    result.u8s[6] = ptr[6];
+    result.u8s[7] = ptr[7];
+    return result;
+#elif defined(_MSC_VER)
+    return *((__unaligned sz_u64_vec_t *)ptr);
+#else
+    __attribute__((aligned(1))) sz_u64_vec_t const *result = (sz_u64_vec_t const *)ptr;
+    return *result;
+#endif
+}
+
+/** @brief  Helper function, using the supplied fixed-capacity buffer to allocate memory. */
+SZ_INTERNAL sz_ptr_t _sz_memory_allocate_fixed(sz_size_t length, void *handle) {
+    sz_size_t capacity;
+    sz_copy((sz_ptr_t)&capacity, (sz_cptr_t)handle, sizeof(sz_size_t));
+    sz_size_t consumed_capacity = sizeof(sz_size_t);
+    if (consumed_capacity + length > capacity) return SZ_NULL;
+    return (sz_ptr_t)handle + consumed_capacity;
+}
+
+/** @brief  Helper "no-op" function, simulating memory deallocation when we use a "static" memory buffer. */
+SZ_INTERNAL void _sz_memory_free_fixed(sz_ptr_t start, sz_size_t length, void *handle) {
+    sz_unused(start && length && handle);
+}
+
+/** @brief  An internal callback used to set a bit in a power-of-two length binary fingerprint of a string. */
+SZ_INTERNAL void _sz_hashes_fingerprint_pow2_callback(sz_cptr_t start, sz_size_t length, sz_u64_t hash, void *handle) {
+    sz_string_view_t *fingerprint_buffer = (sz_string_view_t *)handle;
+    sz_u8_t *fingerprint_u8s = (sz_u8_t *)fingerprint_buffer->start;
+    sz_size_t fingerprint_bytes = fingerprint_buffer->length;
+    fingerprint_u8s[(hash / 8) & (fingerprint_bytes - 1)] |= (1 << (hash & 7));
+    sz_unused(start && length);
+}
+
+/** @brief  An internal callback used to set a bit in a @b non power-of-two length binary fingerprint of a string. */
+SZ_INTERNAL void _sz_hashes_fingerprint_non_pow2_callback(sz_cptr_t start, sz_size_t length, sz_u64_t hash,
+                                                          void *handle) {
+    sz_string_view_t *fingerprint_buffer = (sz_string_view_t *)handle;
+    sz_u8_t *fingerprint_u8s = (sz_u8_t *)fingerprint_buffer->start;
+    sz_size_t fingerprint_bytes = fingerprint_buffer->length;
+    fingerprint_u8s[(hash / 8) % fingerprint_bytes] |= (1 << (hash & 7));
+    sz_unused(start && length);
+}
+
+/** @brief  An internal callback, used to mix all the running hashes into one pointer-size value. */
+SZ_INTERNAL void _sz_hashes_fingerprint_scalar_callback(sz_cptr_t start, sz_size_t length, sz_u64_t hash,
+                                                        void *scalar_handle) {
+    sz_unused(start && length && hash && scalar_handle);
+    sz_size_t *scalar_ptr = (sz_size_t *)scalar_handle;
+    *scalar_ptr ^= hash;
+}
+
+/**
+ *  @brief  Chooses the offsets of the most interesting characters in a search needle.
+ *
+ *  Search throughput can significantly deteriorate if we are matching the wrong characters.
+ *  Say the needle is "aXaYa", and we are comparing the first, second, and last character.
+ *  If we use SIMD and compare many offsets at a time, comparing against "a" in every register is a waste.
+ *
+ *  Similarly, dealing with UTF8 inputs, we know that the lower bits of each character code carry more information.
+ *  Cyrillic alphabet, for example, falls into [0x0410, 0x042F] code range for uppercase [А, Я], and
+ *  into [0x0430, 0x044F] for lowercase [а, я]. Scanning through a text written in Russian, half of the
+ *  bytes will carry absolutely no value and will be equal to 0x04.
+ */
+SZ_INTERNAL void _sz_locate_needle_anomalies(sz_cptr_t start, sz_size_t length, //
+                                             sz_size_t *first, sz_size_t *second, sz_size_t *third) {
+    *first = 0;
+    *second = length / 2;
+    *third = length - 1;
+
+    //
+    int has_duplicates =                   //
+        start[*first] == start[*second] || //
+        start[*first] == start[*third] ||  //
+        start[*second] == start[*third];
+
+    // Loop through letters to find non-colliding variants.
+    if (length > 3 && has_duplicates) {
+        // Pivot the middle point left, until we find a character different from the first one.
+        for (; start[*second] == start[*first] && *second; --(*second)) {}
+        // Pivot the middle point right, until we find a character different from the first one.
+        for (; start[*second] == start[*first] && *second + 1 < *third; ++(*second)) {}
+        // Pivot the third (last) point left, until we find a different character.
+        for (; (start[*third] == start[*second] || start[*third] == start[*first]) && *third > (*second + 1);
+             --(*third)) {}
+    }
+}
+
+#pragma GCC visibility pop
+#pragma endregion
+
+#pragma region Serial Implementation
+
+#if !SZ_AVOID_LIBC
+#include <stdio.h>  // `fprintf`
+#include <stdlib.h> // `malloc`, `EXIT_FAILURE`
+#else
+extern void *malloc(size_t);
+extern void free(void *);
+#endif
+
+SZ_PUBLIC void sz_memory_allocator_init_default(sz_memory_allocator_t *alloc) {
+    alloc->allocate = (sz_memory_allocate_t)malloc;
+    alloc->free = (sz_memory_free_t)free;
+    alloc->handle = SZ_NULL;
+}
+
+SZ_PUBLIC void sz_memory_allocator_init_fixed(sz_memory_allocator_t *alloc, void *buffer, sz_size_t length) {
+    // The logic here is simple - put the buffer length in the first slots of the buffer.
+    // Later use it for bounds checking.
+    alloc->allocate = (sz_memory_allocate_t)_sz_memory_allocate_fixed;
+    alloc->free = (sz_memory_free_t)_sz_memory_free_fixed;
+    alloc->handle = &buffer;
+    sz_copy((sz_ptr_t)buffer, (sz_cptr_t)&length, sizeof(sz_size_t));
+}
+
+/**
+ *  @brief  Byte-level equality comparison between two strings.
+ *          If unaligned loads are allowed, uses a switch-table to avoid loops on short strings.
+ */
+SZ_PUBLIC sz_bool_t sz_equal_serial(sz_cptr_t a, sz_cptr_t b, sz_size_t length) {
+    sz_cptr_t const a_end = a + length;
+    while (a != a_end && *a == *b) a++, b++;
+    return (sz_bool_t)(a_end == a);
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_charset_serial(sz_cptr_t text, sz_size_t length, sz_charset_t const *set) {
+    for (sz_cptr_t const end = text + length; text != end; ++text)
+        if (sz_charset_contains(set, *text)) return text;
+    return SZ_NULL;
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_charset_serial(sz_cptr_t text, sz_size_t length, sz_charset_t const *set) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+    sz_cptr_t const end = text;
+    for (text += length; text != end;)
+        if (sz_charset_contains(set, *(text -= 1))) return text;
+    return SZ_NULL;
+#pragma GCC diagnostic pop
+}
+
+SZ_PUBLIC sz_ordering_t sz_order_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length) {
+    sz_ordering_t ordering_lookup[2] = {sz_greater_k, sz_less_k};
+    sz_bool_t a_shorter = (sz_bool_t)(a_length < b_length);
+    sz_size_t min_length = a_shorter ? a_length : b_length;
+    sz_cptr_t min_end = a + min_length;
+#if SZ_USE_MISALIGNED_LOADS
+    for (sz_u64_vec_t a_vec, b_vec; a + 8 <= min_end; a += 8, b += 8) {
+        a_vec.u64 = sz_u64_bytes_reverse(sz_u64_load(a).u64);
+        b_vec.u64 = sz_u64_bytes_reverse(sz_u64_load(b).u64);
+        if (a_vec.u64 != b_vec.u64) return ordering_lookup[a_vec.u64 < b_vec.u64];
+    }
+#endif
+    for (; a != min_end; ++a, ++b)
+        if (*a != *b) return ordering_lookup[*a < *b];
+    return a_length != b_length ? ordering_lookup[a_shorter] : sz_equal_k;
+}
+
+/**
+ *  @brief  Byte-level equality comparison between two 64-bit integers.
+ *  @return 64-bit integer, where every top bit in each byte signifies a match.
+ */
+SZ_INTERNAL sz_u64_vec_t _sz_u64_each_byte_equal(sz_u64_vec_t a, sz_u64_vec_t b) {
+    sz_u64_vec_t vec;
+    vec.u64 = ~(a.u64 ^ b.u64);
+    // The match is valid, if every bit within each byte is set.
+    // For that take the bottom 7 bits of each byte, add one to them,
+    // and if this sets the top bit to one, then all the 7 bits are ones as well.
+    vec.u64 = ((vec.u64 & 0x7F7F7F7F7F7F7F7Full) + 0x0101010101010101ull) & ((vec.u64 & 0x8080808080808080ull));
+    return vec;
+}
+
+/**
+ *  @brief  Find the first occurrence of a @b single-character needle in an arbitrary length haystack.
+ *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
+ *          Identical to `memchr(haystack, needle[0], haystack_length)`.
+ */
+SZ_PUBLIC sz_cptr_t sz_find_byte_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+
+    if (!h_length) return SZ_NULL;
+    sz_cptr_t const h_end = h + h_length;
+
+#if !SZ_USE_MISALIGNED_LOADS
+    // Process the misaligned head, to void UB on unaligned 64-bit loads.
+    for (; ((sz_size_t)h & 7ull) && h < h_end; ++h)
+        if (*h == *n) return h;
+#endif
+
+    // Broadcast the n into every byte of a 64-bit integer to use SWAR
+    // techniques and process eight characters at a time.
+    sz_u64_vec_t h_vec, n_vec, match_vec;
+    match_vec.u64 = 0;
+    n_vec.u64 = (sz_u64_t)n[0] * 0x0101010101010101ull;
+    for (; h + 8 <= h_end; h += 8) {
+        h_vec.u64 = *(sz_u64_t const *)h;
+        match_vec = _sz_u64_each_byte_equal(h_vec, n_vec);
+        if (match_vec.u64) return h + sz_u64_ctz(match_vec.u64) / 8;
+    }
+
+    // Handle the misaligned tail.
+    for (; h < h_end; ++h)
+        if (*h == *n) return h;
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  Find the last occurrence of a @b single-character needle in an arbitrary length haystack.
+ *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
+ *          Identical to `memrchr(haystack, needle[0], haystack_length)`.
+ */
+sz_cptr_t sz_rfind_byte_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+
+    if (!h_length) return SZ_NULL;
+    sz_cptr_t const h_start = h;
+
+    // Reposition the `h` pointer to the end, as we will be walking backwards.
+    h = h + h_length - 1;
+
+#if !SZ_USE_MISALIGNED_LOADS
+    // Process the misaligned head, to void UB on unaligned 64-bit loads.
+    for (; ((sz_size_t)(h + 1) & 7ull) && h >= h_start; --h)
+        if (*h == *n) return h;
+#endif
+
+    // Broadcast the n into every byte of a 64-bit integer to use SWAR
+    // techniques and process eight characters at a time.
+    sz_u64_vec_t h_vec, n_vec, match_vec;
+    n_vec.u64 = (sz_u64_t)n[0] * 0x0101010101010101ull;
+    for (; h >= h_start + 7; h -= 8) {
+        h_vec.u64 = *(sz_u64_t const *)(h - 7);
+        match_vec = _sz_u64_each_byte_equal(h_vec, n_vec);
+        if (match_vec.u64) return h - sz_u64_clz(match_vec.u64) / 8;
+    }
+
+    for (; h >= h_start; --h)
+        if (*h == *n) return h;
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  2Byte-level equality comparison between two 64-bit integers.
+ *  @return 64-bit integer, where every top bit in each 2byte signifies a match.
+ */
+SZ_INTERNAL sz_u64_vec_t _sz_u64_each_2byte_equal(sz_u64_vec_t a, sz_u64_vec_t b) {
+    sz_u64_vec_t vec;
+    vec.u64 = ~(a.u64 ^ b.u64);
+    // The match is valid, if every bit within each 2byte is set.
+    // For that take the bottom 15 bits of each 2byte, add one to them,
+    // and if this sets the top bit to one, then all the 15 bits are ones as well.
+    vec.u64 = ((vec.u64 & 0x7FFF7FFF7FFF7FFFull) + 0x0001000100010001ull) & ((vec.u64 & 0x8000800080008000ull));
+    return vec;
+}
+
+/**
+ *  @brief  Find the first occurrence of a @b two-character needle in an arbitrary length haystack.
+ *          This implementation uses hardware-agnostic SWAR technique, to process 8 possible offsets at a time.
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_2byte_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+
+    // This is an internal method, and the haystack is guaranteed to be at least 2 bytes long.
+    sz_assert(h_length >= 2 && "The haystack is too short.");
+    sz_cptr_t const h_end = h + h_length;
+
+#if !SZ_USE_MISALIGNED_LOADS
+    // Process the misaligned head, to void UB on unaligned 64-bit loads.
+    for (; ((sz_size_t)h & 7ull) && h + 2 <= h_end; ++h)
+        if ((h[0] == n[0]) + (h[1] == n[1]) == 2) return h;
+#endif
+
+    sz_u64_vec_t h_even_vec, h_odd_vec, n_vec, matches_even_vec, matches_odd_vec;
+    n_vec.u64 = 0;
+    n_vec.u8s[0] = n[0], n_vec.u8s[1] = n[1];
+    n_vec.u64 *= 0x0001000100010001ull; // broadcast
+
+    // This code simulates hyper-scalar execution, analyzing 8 offsets at a time.
+    for (; h + 9 <= h_end; h += 8) {
+        h_even_vec.u64 = *(sz_u64_t *)h;
+        h_odd_vec.u64 = (h_even_vec.u64 >> 8) | ((sz_u64_t)h[8] << 56);
+        matches_even_vec = _sz_u64_each_2byte_equal(h_even_vec, n_vec);
+        matches_odd_vec = _sz_u64_each_2byte_equal(h_odd_vec, n_vec);
+
+        matches_even_vec.u64 >>= 8;
+        if (matches_even_vec.u64 + matches_odd_vec.u64) {
+            sz_u64_t match_indicators = matches_even_vec.u64 | matches_odd_vec.u64;
+            return h + sz_u64_ctz(match_indicators) / 8;
+        }
+    }
+
+    for (; h + 2 <= h_end; ++h)
+        if ((h[0] == n[0]) + (h[1] == n[1]) == 2) return h;
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  4Byte-level equality comparison between two 64-bit integers.
+ *  @return 64-bit integer, where every top bit in each 4byte signifies a match.
+ */
+SZ_INTERNAL sz_u64_vec_t _sz_u64_each_4byte_equal(sz_u64_vec_t a, sz_u64_vec_t b) {
+    sz_u64_vec_t vec;
+    vec.u64 = ~(a.u64 ^ b.u64);
+    // The match is valid, if every bit within each 4byte is set.
+    // For that take the bottom 31 bits of each 4byte, add one to them,
+    // and if this sets the top bit to one, then all the 31 bits are ones as well.
+    vec.u64 = ((vec.u64 & 0x7FFFFFFF7FFFFFFFull) + 0x0000000100000001ull) & ((vec.u64 & 0x8000000080000000ull));
+    return vec;
+}
+
+/**
+ *  @brief  Find the first occurrence of a @b four-character needle in an arbitrary length haystack.
+ *          This implementation uses hardware-agnostic SWAR technique, to process 8 possible offsets at a time.
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_4byte_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+
+    // This is an internal method, and the haystack is guaranteed to be at least 4 bytes long.
+    sz_assert(h_length >= 4 && "The haystack is too short.");
+    sz_cptr_t const h_end = h + h_length;
+
+#if !SZ_USE_MISALIGNED_LOADS
+    // Process the misaligned head, to void UB on unaligned 64-bit loads.
+    for (; ((sz_size_t)h & 7ull) && h + 4 <= h_end; ++h)
+        if ((h[0] == n[0]) + (h[1] == n[1]) + (h[2] == n[2]) + (h[3] == n[3]) == 4) return h;
+#endif
+
+    sz_u64_vec_t h0_vec, h1_vec, h2_vec, h3_vec, n_vec, matches0_vec, matches1_vec, matches2_vec, matches3_vec;
+    n_vec.u64 = 0;
+    n_vec.u8s[0] = n[0], n_vec.u8s[1] = n[1], n_vec.u8s[2] = n[2], n_vec.u8s[3] = n[3];
+    n_vec.u64 *= 0x0000000100000001ull; // broadcast
+
+    // This code simulates hyper-scalar execution, analyzing 8 offsets at a time using four 64-bit words.
+    // We load the subsequent four-byte word as well, taking its first bytes. Think of it as a glorified prefetch :)
+    sz_u64_t h_page_current, h_page_next;
+    for (; h + sizeof(sz_u64_t) + sizeof(sz_u32_t) <= h_end; h += sizeof(sz_u64_t)) {
+        h_page_current = *(sz_u64_t *)h;
+        h_page_next = *(sz_u32_t *)(h + 8);
+        h0_vec.u64 = (h_page_current);
+        h1_vec.u64 = (h_page_current >> 8) | (h_page_next << 56);
+        h2_vec.u64 = (h_page_current >> 16) | (h_page_next << 48);
+        h3_vec.u64 = (h_page_current >> 24) | (h_page_next << 40);
+        matches0_vec = _sz_u64_each_4byte_equal(h0_vec, n_vec);
+        matches1_vec = _sz_u64_each_4byte_equal(h1_vec, n_vec);
+        matches2_vec = _sz_u64_each_4byte_equal(h2_vec, n_vec);
+        matches3_vec = _sz_u64_each_4byte_equal(h3_vec, n_vec);
+
+        if (matches0_vec.u64 | matches1_vec.u64 | matches2_vec.u64 | matches3_vec.u64) {
+            matches0_vec.u64 >>= 24;
+            matches1_vec.u64 >>= 16;
+            matches2_vec.u64 >>= 8;
+            sz_u64_t match_indicators = matches0_vec.u64 | matches1_vec.u64 | matches2_vec.u64 | matches3_vec.u64;
+            return h + sz_u64_ctz(match_indicators) / 8;
+        }
+    }
+
+    for (; h + 4 <= h_end; ++h)
+        if ((h[0] == n[0]) + (h[1] == n[1]) + (h[2] == n[2]) + (h[3] == n[3]) == 4) return h;
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  3Byte-level equality comparison between two 64-bit integers.
+ *  @return 64-bit integer, where every top bit in each 3byte signifies a match.
+ */
+SZ_INTERNAL sz_u64_vec_t _sz_u64_each_3byte_equal(sz_u64_vec_t a, sz_u64_vec_t b) {
+    sz_u64_vec_t vec;
+    vec.u64 = ~(a.u64 ^ b.u64);
+    // The match is valid, if every bit within each 4byte is set.
+    // For that take the bottom 31 bits of each 4byte, add one to them,
+    // and if this sets the top bit to one, then all the 31 bits are ones as well.
+    vec.u64 = ((vec.u64 & 0xFFFF7FFFFF7FFFFFull) + 0x0000000001000001ull) & ((vec.u64 & 0x0000800000800000ull));
+    return vec;
+}
+
+/**
+ *  @brief  Find the first occurrence of a @b three-character needle in an arbitrary length haystack.
+ *          This implementation uses hardware-agnostic SWAR technique, to process 8 possible offsets at a time.
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_3byte_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+
+    // This is an internal method, and the haystack is guaranteed to be at least 4 bytes long.
+    sz_assert(h_length >= 3 && "The haystack is too short.");
+    sz_cptr_t const h_end = h + h_length;
+
+#if !SZ_USE_MISALIGNED_LOADS
+    // Process the misaligned head, to void UB on unaligned 64-bit loads.
+    for (; ((sz_size_t)h & 7ull) && h + 3 <= h_end; ++h)
+        if ((h[0] == n[0]) + (h[1] == n[1]) + (h[2] == n[2]) == 3) return h;
+#endif
+
+    // We fetch 12
+    sz_u64_vec_t h0_vec, h1_vec, h2_vec, h3_vec, h4_vec;
+    sz_u64_vec_t matches0_vec, matches1_vec, matches2_vec, matches3_vec, matches4_vec;
+    sz_u64_vec_t n_vec;
+    n_vec.u64 = 0;
+    n_vec.u8s[0] = n[0], n_vec.u8s[1] = n[1], n_vec.u8s[2] = n[2], n_vec.u8s[3] = n[3];
+    n_vec.u64 *= 0x0000000001000001ull; // broadcast
+
+    // This code simulates hyper-scalar execution, analyzing 8 offsets at a time using three 64-bit words.
+    // We load the subsequent two-byte word as well.
+    sz_u64_t h_page_current, h_page_next;
+    for (; h + sizeof(sz_u64_t) + sizeof(sz_u16_t) <= h_end; h += sizeof(sz_u64_t)) {
+        h_page_current = *(sz_u64_t *)h;
+        h_page_next = *(sz_u16_t *)(h + 8);
+        h0_vec.u64 = (h_page_current);
+        h1_vec.u64 = (h_page_current >> 8) | (h_page_next << 56);
+        h2_vec.u64 = (h_page_current >> 16) | (h_page_next << 48);
+        h3_vec.u64 = (h_page_current >> 24) | (h_page_next << 40);
+        h4_vec.u64 = (h_page_current >> 32) | (h_page_next << 32);
+        matches0_vec = _sz_u64_each_3byte_equal(h0_vec, n_vec);
+        matches1_vec = _sz_u64_each_3byte_equal(h1_vec, n_vec);
+        matches2_vec = _sz_u64_each_3byte_equal(h2_vec, n_vec);
+        matches3_vec = _sz_u64_each_3byte_equal(h3_vec, n_vec);
+        matches4_vec = _sz_u64_each_3byte_equal(h4_vec, n_vec);
+
+        if (matches0_vec.u64 | matches1_vec.u64 | matches2_vec.u64 | matches3_vec.u64 | matches4_vec.u64) {
+            matches0_vec.u64 >>= 16;
+            matches1_vec.u64 >>= 8;
+            matches3_vec.u64 <<= 8;
+            matches4_vec.u64 <<= 16;
+            sz_u64_t match_indicators =
+                matches0_vec.u64 | matches1_vec.u64 | matches2_vec.u64 | matches3_vec.u64 | matches4_vec.u64;
+            return h + sz_u64_ctz(match_indicators) / 8;
+        }
+    }
+
+    for (; h + 3 <= h_end; ++h)
+        if ((h[0] == n[0]) + (h[1] == n[1]) + (h[2] == n[2]) == 3) return h;
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  Boyer-Moore-Horspool algorithm for exact matching of patterns up to @b 256-bytes long.
+ *          Uses the Raita heuristic to match the first two, the last, and the middle character of the pattern.
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_horspool_upto_256bytes_serial(sz_cptr_t h_chars, sz_size_t h_length, //
+                                                             sz_cptr_t n_chars, sz_size_t n_length) {
+    sz_assert(n_length <= 256 && "The pattern is too long.");
+    // Several popular string matching algorithms are using a bad-character shift table.
+    // Boyer Moore: https://www-igm.univ-mlv.fr/~lecroq/string/node14.html
+    // Quick Search: https://www-igm.univ-mlv.fr/~lecroq/string/node19.html
+    // Smith: https://www-igm.univ-mlv.fr/~lecroq/string/node21.html
+    union {
+        sz_u8_t jumps[256];
+        sz_u64_vec_t vecs[64];
+    } bad_shift_table;
+
+    // Let's initialize the table using SWAR to the total length of the string.
+    sz_u8_t const *h = (sz_u8_t const *)h_chars;
+    sz_u8_t const *n = (sz_u8_t const *)n_chars;
+    {
+        sz_u64_vec_t n_length_vec;
+        n_length_vec.u64 = n_length;
+        n_length_vec.u64 *= 0x0101010101010101ull; // broadcast
+        for (sz_size_t i = 0; i != 64; ++i) bad_shift_table.vecs[i].u64 = n_length_vec.u64;
+        for (sz_size_t i = 0; i + 1 < n_length; ++i) bad_shift_table.jumps[n[i]] = (sz_u8_t)(n_length - i - 1);
+    }
+
+    // Another common heuristic is to match a few characters from different parts of a string.
+    // Raita suggests to use the first two, the last, and the middle character of the pattern.
+    sz_u32_vec_t h_vec, n_vec;
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n_chars, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into an unsigned integer.
+    n_vec.u8s[0] = n[offset_first];
+    n_vec.u8s[1] = n[offset_first + 1];
+    n_vec.u8s[2] = n[offset_mid];
+    n_vec.u8s[3] = n[offset_last];
+
+    // Scan through the whole haystack, skipping the last `n_length - 1` bytes.
+    for (sz_size_t i = 0; i <= h_length - n_length;) {
+        h_vec.u8s[0] = h[i + offset_first];
+        h_vec.u8s[1] = h[i + offset_first + 1];
+        h_vec.u8s[2] = h[i + offset_mid];
+        h_vec.u8s[3] = h[i + offset_last];
+        if (h_vec.u32 == n_vec.u32 && sz_equal((sz_cptr_t)h + i, n_chars, n_length)) return (sz_cptr_t)h + i;
+        i += bad_shift_table.jumps[h[i + n_length - 1]];
+    }
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  Boyer-Moore-Horspool algorithm for @b reverse-order exact matching of patterns up to @b 256-bytes long.
+ *          Uses the Raita heuristic to match the first two, the last, and the middle character of the pattern.
+ */
+SZ_INTERNAL sz_cptr_t _sz_rfind_horspool_upto_256bytes_serial(sz_cptr_t h_chars, sz_size_t h_length, //
+                                                              sz_cptr_t n_chars, sz_size_t n_length) {
+    sz_assert(n_length <= 256 && "The pattern is too long.");
+    union {
+        sz_u8_t jumps[256];
+        sz_u64_vec_t vecs[64];
+    } bad_shift_table;
+
+    // Let's initialize the table using SWAR to the total length of the string.
+    sz_u8_t const *h = (sz_u8_t const *)h_chars;
+    sz_u8_t const *n = (sz_u8_t const *)n_chars;
+    {
+        sz_u64_vec_t n_length_vec;
+        n_length_vec.u64 = n_length;
+        n_length_vec.u64 *= 0x0101010101010101ull; // broadcast
+        for (sz_size_t i = 0; i != 64; ++i) bad_shift_table.vecs[i].u64 = n_length_vec.u64;
+        for (sz_size_t i = 0; i + 1 < n_length; ++i)
+            bad_shift_table.jumps[n[n_length - i - 1]] = (sz_u8_t)(n_length - i - 1);
+    }
+
+    // Another common heuristic is to match a few characters from different parts of a string.
+    // Raita suggests to use the first two, the last, and the middle character of the pattern.
+    sz_u32_vec_t h_vec, n_vec;
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n_chars, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into an unsigned integer.
+    n_vec.u8s[0] = n[offset_first];
+    n_vec.u8s[1] = n[offset_first + 1];
+    n_vec.u8s[2] = n[offset_mid];
+    n_vec.u8s[3] = n[offset_last];
+
+    // Scan through the whole haystack, skipping the first `n_length - 1` bytes.
+    for (sz_size_t j = 0; j <= h_length - n_length;) {
+        sz_size_t i = h_length - n_length - j;
+        h_vec.u8s[0] = h[i + offset_first];
+        h_vec.u8s[1] = h[i + offset_first + 1];
+        h_vec.u8s[2] = h[i + offset_mid];
+        h_vec.u8s[3] = h[i + offset_last];
+        if (h_vec.u32 == n_vec.u32 && sz_equal((sz_cptr_t)h + i, n_chars, n_length)) return (sz_cptr_t)h + i;
+        j += bad_shift_table.jumps[h[i]];
+    }
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  Exact substring search helper function, that finds the first occurrence of a prefix of the needle
+ *          using a given search function, and then verifies the remaining part of the needle.
+ */
+SZ_INTERNAL sz_cptr_t _sz_find_with_prefix(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length,
+                                           sz_find_t find_prefix, sz_size_t prefix_length) {
+
+    sz_size_t suffix_length = n_length - prefix_length;
+    while (1) {
+        sz_cptr_t found = find_prefix(h, h_length, n, prefix_length);
+        if (!found) return SZ_NULL;
+
+        // Verify the remaining part of the needle
+        sz_size_t remaining = h_length - (found - h);
+        if (remaining < suffix_length) return SZ_NULL;
+        if (sz_equal(found + prefix_length, n + prefix_length, suffix_length)) return found;
+
+        // Adjust the position.
+        h = found + 1;
+        h_length = remaining - 1;
+    }
+
+    // Unreachable, but helps silence compiler warnings:
+    return SZ_NULL;
+}
+
+/**
+ *  @brief  Exact reverse-order substring search helper function, that finds the last occurrence of a suffix of the
+ *          needle using a given search function, and then verifies the remaining part of the needle.
+ */
+SZ_INTERNAL sz_cptr_t _sz_rfind_with_suffix(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length,
+                                            sz_find_t find_suffix, sz_size_t suffix_length) {
+
+    sz_size_t prefix_length = n_length - suffix_length;
+    while (1) {
+        sz_cptr_t found = find_suffix(h, h_length, n + prefix_length, suffix_length);
+        if (!found) return SZ_NULL;
+
+        // Verify the remaining part of the needle
+        sz_size_t remaining = found - h;
+        if (remaining < prefix_length) return SZ_NULL;
+        if (sz_equal(found - prefix_length, n, prefix_length)) return found - prefix_length;
+
+        // Adjust the position.
+        h_length = remaining - 1;
+    }
+
+    // Unreachable, but helps silence compiler warnings:
+    return SZ_NULL;
+}
+
+SZ_INTERNAL sz_cptr_t _sz_find_over_4bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    return _sz_find_with_prefix(h, h_length, n, n_length, (sz_find_t)_sz_find_4byte_serial, 4);
+}
+
+SZ_INTERNAL sz_cptr_t _sz_find_horspool_over_256bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                             sz_size_t n_length) {
+    return _sz_find_with_prefix(h, h_length, n, n_length, _sz_find_horspool_upto_256bytes_serial, 256);
+}
+
+SZ_INTERNAL sz_cptr_t _sz_rfind_horspool_over_256bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
+                                                              sz_size_t n_length) {
+    return _sz_rfind_with_suffix(h, h_length, n, n_length, _sz_rfind_horspool_upto_256bytes_serial, 256);
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+
+    sz_find_t backends[] = {
+        // For very short strings brute-force SWAR makes sense.
+        (sz_find_t)sz_find_byte_serial,
+        (sz_find_t)_sz_find_2byte_serial,
+        (sz_find_t)_sz_find_3byte_serial,
+        (sz_find_t)_sz_find_4byte_serial,
+        // To avoid constructing the skip-table, let's use the prefixed approach.
+        (sz_find_t)_sz_find_over_4bytes_serial,
+        // For longer needles - use skip tables.
+        (sz_find_t)_sz_find_horspool_upto_256bytes_serial,
+        (sz_find_t)_sz_find_horspool_over_256bytes_serial,
+    };
+
+    return backends[
+        // For very short strings brute-force SWAR makes sense.
+        (n_length > 1) + (n_length > 2) + (n_length > 3) +
+        // To avoid constructing the skip-table, let's use the prefixed approach.
+        (n_length > 4) +
+        // For longer needles - use skip tables.
+        (n_length > 8) + (n_length > 256)](h, h_length, n, n_length);
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+
+    sz_find_t backends[] = {
+        // For very short strings brute-force SWAR makes sense.
+        (sz_find_t)sz_rfind_byte_serial,
+        //  TODO: implement reverse-order SWAR for 2/3/4 byte variants.
+        //  TODO: (sz_find_t)_sz_rfind_2byte_serial,
+        //  TODO: (sz_find_t)_sz_rfind_3byte_serial,
+        //  TODO: (sz_find_t)_sz_rfind_4byte_serial,
+        // To avoid constructing the skip-table, let's use the prefixed approach.
+        // (sz_find_t)_sz_rfind_over_4bytes_serial,
+        // For longer needles - use skip tables.
+        (sz_find_t)_sz_rfind_horspool_upto_256bytes_serial,
+        (sz_find_t)_sz_rfind_horspool_over_256bytes_serial,
+    };
+
+    return backends[
+        // For very short strings brute-force SWAR makes sense.
+        0 +
+        // To avoid constructing the skip-table, let's use the prefixed approach.
+        (n_length > 1) +
+        // For longer needles - use skip tables.
+        (n_length > 256)](h, h_length, n, n_length);
+}
+
+SZ_INTERNAL sz_size_t _sz_edit_distance_skewed_diagonals_serial( //
+    sz_cptr_t shorter, sz_size_t shorter_length,                 //
+    sz_cptr_t longer, sz_size_t longer_length,                   //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+
+    // Simplify usage in higher-level libraries, where wrapping custom allocators may be troublesome.
+    sz_memory_allocator_t global_alloc;
+    if (!alloc) {
+        sz_memory_allocator_init_default(&global_alloc);
+        alloc = &global_alloc;
+    }
+
+    // TODO: Generalize to remove the following asserts!
+    sz_assert(!bound && "For bounded search the method should only evaluate one band of the matrix.");
+    sz_assert(shorter_length == longer_length && "The method hasn't been generalized to different length inputs yet.");
+    sz_unused(longer_length && bound);
+
+    // We are going to store 3 diagonals of the matrix.
+    // The length of the longest (main) diagonal would be `n = (shorter_length + 1)`.
+    sz_size_t n = shorter_length + 1;
+    sz_size_t buffer_length = sizeof(sz_size_t) * n * 3;
+    sz_size_t *distances = (sz_size_t *)alloc->allocate(buffer_length, alloc->handle);
+    if (!distances) return SZ_SIZE_MAX;
+
+    sz_size_t *previous_distances = distances;
+    sz_size_t *current_distances = previous_distances + n;
+    sz_size_t *next_distances = previous_distances + n * 2;
+
+    // Initialize the first two diagonals:
+    previous_distances[0] = 0;
+    current_distances[0] = current_distances[1] = 1;
+
+    // Progress through the upper triangle of the Levenshtein matrix.
+    sz_size_t next_skew_diagonal_index = 2;
+    for (; next_skew_diagonal_index != n; ++next_skew_diagonal_index) {
+        sz_size_t const next_skew_diagonal_length = next_skew_diagonal_index + 1;
+        for (sz_size_t i = 0; i + 2 < next_skew_diagonal_length; ++i) {
+            sz_size_t cost_of_substitution = shorter[next_skew_diagonal_index - i - 2] != longer[i];
+            sz_size_t cost_if_substitution = previous_distances[i] + cost_of_substitution;
+            sz_size_t cost_if_deletion_or_insertion = sz_min_of_two(current_distances[i], current_distances[i + 1]) + 1;
+            next_distances[i + 1] = sz_min_of_two(cost_if_deletion_or_insertion, cost_if_substitution);
+        }
+        // Don't forget to populate the first row and the fiest column of the Levenshtein matrix.
+        next_distances[0] = next_distances[next_skew_diagonal_length - 1] = next_skew_diagonal_index;
+        // Perform a circular rotarion of those buffers, to reuse the memory.
+        sz_size_t *temporary = previous_distances;
+        previous_distances = current_distances;
+        current_distances = next_distances;
+        next_distances = temporary;
+    }
+
+    // By now we've scanned through the upper triangle of the matrix, where each subsequent iteration results in a
+    // larger diagonal. From now onwards, we will be shrinking. Instead of adding value equal to the skewed diagonal
+    // index on either side, we will be cropping those values out.
+    sz_size_t total_diagonals = n + n - 1;
+    for (; next_skew_diagonal_index != total_diagonals; ++next_skew_diagonal_index) {
+        sz_size_t const next_skew_diagonal_length = total_diagonals - next_skew_diagonal_index;
+        for (sz_size_t i = 0; i != next_skew_diagonal_length; ++i) {
+            sz_size_t cost_of_substitution =
+                shorter[shorter_length - 1 - i] != longer[next_skew_diagonal_index - n + i];
+            sz_size_t cost_if_substitution = previous_distances[i] + cost_of_substitution;
+            sz_size_t cost_if_deletion_or_insertion = sz_min_of_two(current_distances[i], current_distances[i + 1]) + 1;
+            next_distances[i] = sz_min_of_two(cost_if_deletion_or_insertion, cost_if_substitution);
+        }
+        // Perform a circular rotarion of those buffers, to reuse the memory, this time, with a shift,
+        // dropping the first element in the current array.
+        sz_size_t *temporary = previous_distances;
+        previous_distances = current_distances + 1;
+        current_distances = next_distances;
+        next_distances = temporary;
+    }
+
+    // Cache scalar before `free` call.
+    sz_size_t result = current_distances[0];
+    alloc->free(distances, buffer_length, alloc->handle);
+    return result;
+}
+
+SZ_INTERNAL sz_size_t _sz_edit_distance_wagner_fisher_serial( //
+    sz_cptr_t longer, sz_size_t longer_length,                //
+    sz_cptr_t shorter, sz_size_t shorter_length,              //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+
+    // Simplify usage in higher-level libraries, where wrapping custom allocators may be troublesome.
+    sz_memory_allocator_t global_alloc;
+    if (!alloc) {
+        sz_memory_allocator_init_default(&global_alloc);
+        alloc = &global_alloc;
+    }
+
+    // If a buffering memory-allocator is provided, this operation is practically free,
+    // and cheaper than allocating even 512 bytes (for small distance matrices) on stack.
+    sz_size_t buffer_length = sizeof(sz_size_t) * ((shorter_length + 1) * 2);
+    sz_size_t *distances = (sz_size_t *)alloc->allocate(buffer_length, alloc->handle);
+    if (!distances) return SZ_SIZE_MAX;
+
+    sz_size_t *previous_distances = distances;
+    sz_size_t *current_distances = previous_distances + shorter_length + 1;
+
+    for (sz_size_t idx_shorter = 0; idx_shorter != (shorter_length + 1); ++idx_shorter)
+        previous_distances[idx_shorter] = idx_shorter;
+
+    // Keeping track of the bound parameter introduces a very noticeable performance penalty.
+    // So if it's not provided, we can skip the check altogether.
+    if (!bound) {
+        for (sz_size_t idx_longer = 0; idx_longer != longer_length; ++idx_longer) {
+            current_distances[0] = idx_longer + 1;
+            for (sz_size_t idx_shorter = 0; idx_shorter != shorter_length; ++idx_shorter) {
+                sz_size_t cost_deletion = previous_distances[idx_shorter + 1] + 1;
+                sz_size_t cost_insertion = current_distances[idx_shorter] + 1;
+                sz_size_t cost_substitution =
+                    previous_distances[idx_shorter] + (longer[idx_longer] != shorter[idx_shorter]);
+                // ? It might be a good idea to enforce branchless execution here.
+                // ? The caveat being that the benchmarks on longer sequences backfire and more research is needed.
+                current_distances[idx_shorter + 1] = sz_min_of_three(cost_deletion, cost_insertion, cost_substitution);
+            }
+            sz_u64_swap((sz_u64_t *)&previous_distances, (sz_u64_t *)&current_distances);
+        }
+        // Cache scalar before `free` call.
+        sz_size_t result = previous_distances[shorter_length];
+        alloc->free(distances, buffer_length, alloc->handle);
+        return result;
+    }
+    //
+    else {
+        for (sz_size_t idx_longer = 0; idx_longer != longer_length; ++idx_longer) {
+            current_distances[0] = idx_longer + 1;
+
+            // Initialize min_distance with a value greater than bound
+            sz_size_t min_distance = bound - 1;
+
+            for (sz_size_t idx_shorter = 0; idx_shorter != shorter_length; ++idx_shorter) {
+                sz_size_t cost_deletion = previous_distances[idx_shorter + 1] + 1;
+                sz_size_t cost_insertion = current_distances[idx_shorter] + 1;
+                sz_size_t cost_substitution =
+                    previous_distances[idx_shorter] + (longer[idx_longer] != shorter[idx_shorter]);
+                current_distances[idx_shorter + 1] = sz_min_of_three(cost_deletion, cost_insertion, cost_substitution);
+
+                // Keep track of the minimum distance seen so far in this row
+                min_distance = sz_min_of_two(current_distances[idx_shorter + 1], min_distance);
+            }
+
+            // If the minimum distance in this row exceeded the bound, return early
+            if (min_distance >= bound) {
+                alloc->free(distances, buffer_length, alloc->handle);
+                return bound;
+            }
+
+            // Swap previous_distances and current_distances pointers
+            sz_u64_swap((sz_u64_t *)&previous_distances, (sz_u64_t *)&current_distances);
+        }
+        // Cache scalar before `free` call.
+        sz_size_t result = previous_distances[shorter_length] < bound ? previous_distances[shorter_length] : bound;
+        alloc->free(distances, buffer_length, alloc->handle);
+        return result;
+    }
+}
+
+SZ_PUBLIC sz_size_t sz_edit_distance_serial(     //
+    sz_cptr_t longer, sz_size_t longer_length,   //
+    sz_cptr_t shorter, sz_size_t shorter_length, //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+
+    // Let's make sure that we use the amount proportional to the
+    // number of elements in the shorter string, not the larger.
+    if (shorter_length > longer_length) {
+        sz_u64_swap((sz_u64_t *)&longer_length, (sz_u64_t *)&shorter_length);
+        sz_u64_swap((sz_u64_t *)&longer, (sz_u64_t *)&shorter);
+    }
+
+    // Skip the matching prefixes and suffixes, they won't affect the distance.
+    for (sz_cptr_t a_end = longer + longer_length, b_end = shorter + shorter_length;
+         longer != a_end && shorter != b_end && *longer == *shorter;
+         ++longer, ++shorter, --longer_length, --shorter_length)
+        ;
+    for (; longer_length && shorter_length && longer[longer_length - 1] == shorter[shorter_length - 1];
+         --longer_length, --shorter_length)
+        ;
+
+    // Bounded computations may exit early.
+    if (bound) {
+        // If one of the strings is empty - the edit distance is equal to the length of the other one.
+        if (longer_length == 0) return shorter_length <= bound ? shorter_length : bound;
+        if (shorter_length == 0) return longer_length <= bound ? longer_length : bound;
+        // If the difference in length is beyond the `bound`, there is no need to check at all.
+        if (longer_length - shorter_length > bound) return bound;
+    }
+
+    if (shorter_length == 0) return longer_length; // If no mismatches were found - the distance is zero.
+    if (shorter_length == longer_length && !bound)
+        return _sz_edit_distance_skewed_diagonals_serial(longer, longer_length, shorter, shorter_length, bound, alloc);
+    return _sz_edit_distance_wagner_fisher_serial(longer, longer_length, shorter, shorter_length, bound, alloc);
+}
+
+SZ_PUBLIC sz_ssize_t sz_alignment_score_serial(       //
+    sz_cptr_t longer, sz_size_t longer_length,        //
+    sz_cptr_t shorter, sz_size_t shorter_length,      //
+    sz_error_cost_t const *subs, sz_error_cost_t gap, //
+    sz_memory_allocator_t *alloc) {
+
+    // If one of the strings is empty - the edit distance is equal to the length of the other one
+    if (longer_length == 0) return (sz_ssize_t)shorter_length * gap;
+    if (shorter_length == 0) return (sz_ssize_t)longer_length * gap;
+
+    // Let's make sure that we use the amount proportional to the
+    // number of elements in the shorter string, not the larger.
+    if (shorter_length > longer_length) {
+        sz_u64_swap((sz_u64_t *)&longer_length, (sz_u64_t *)&shorter_length);
+        sz_u64_swap((sz_u64_t *)&longer, (sz_u64_t *)&shorter);
+    }
+
+    // Simplify usage in higher-level libraries, where wrapping custom allocators may be troublesome.
+    sz_memory_allocator_t global_alloc;
+    if (!alloc) {
+        sz_memory_allocator_init_default(&global_alloc);
+        alloc = &global_alloc;
+    }
+
+    sz_size_t n = shorter_length + 1;
+    sz_size_t buffer_length = sizeof(sz_ssize_t) * n * 2;
+    sz_ssize_t *distances = (sz_ssize_t *)alloc->allocate(buffer_length, alloc->handle);
+    sz_ssize_t *previous_distances = distances;
+    sz_ssize_t *current_distances = previous_distances + n;
+
+    for (sz_size_t idx_shorter = 0; idx_shorter != n; ++idx_shorter)
+        previous_distances[idx_shorter] = (sz_ssize_t)idx_shorter * gap;
+
+    sz_u8_t const *shorter_unsigned = (sz_u8_t const *)shorter;
+    sz_u8_t const *longer_unsigned = (sz_u8_t const *)longer;
+    for (sz_size_t idx_longer = 0; idx_longer != longer_length; ++idx_longer) {
+        current_distances[0] = ((sz_ssize_t)idx_longer + 1) * gap;
+
+        // Initialize min_distance with a value greater than bound
+        sz_error_cost_t const *a_subs = subs + longer_unsigned[idx_longer] * 256ul;
+        for (sz_size_t idx_shorter = 0; idx_shorter != shorter_length; ++idx_shorter) {
+            sz_ssize_t cost_deletion = previous_distances[idx_shorter + 1] + gap;
+            sz_ssize_t cost_insertion = current_distances[idx_shorter] + gap;
+            sz_ssize_t cost_substitution = previous_distances[idx_shorter] + a_subs[shorter_unsigned[idx_shorter]];
+            current_distances[idx_shorter + 1] = sz_max_of_three(cost_deletion, cost_insertion, cost_substitution);
+        }
+
+        // Swap previous_distances and current_distances pointers
+        sz_u64_swap((sz_u64_t *)&previous_distances, (sz_u64_t *)&current_distances);
+    }
+
+    // Cache scalar before `free` call.
+    sz_ssize_t result = previous_distances[shorter_length];
+    alloc->free(distances, buffer_length, alloc->handle);
+    return result;
+}
+
+/**
+ *  @brief  Largest prime number that fits into 31 bits.
+ *  @see    https://mersenneforum.org/showthread.php?t=3471
+ */
+#define SZ_U32_MAX_PRIME (2147483647u)
+
+/**
+ *  @brief  Largest prime number that fits into 64 bits.
+ *  @see    https://mersenneforum.org/showthread.php?t=3471
+ *
+ *  2^64 = 18,446,744,073,709,551,616
+ *  this = 18,446,744,073,709,551,557
+ *  diff = 59
+ */
+#define SZ_U64_MAX_PRIME (18446744073709551557ull)
+
+/*
+ *  One hardware-accelerated way of mixing hashes can be CRC, but it's only implemented for 32-bit values.
+ *  Using a Boost-like mixer works very poorly in such case:
+ *
+ *       hash_first ^ (hash_second + 0x517cc1b727220a95 + (hash_first << 6) + (hash_first >> 2));
+ *
+ *  Let's stick to the Fibonacci hash trick using the golden ratio.
+ *  https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
+ */
+#define _sz_hash_mix(first, second) ((first * 11400714819323198485ull) ^ (second * 11400714819323198485ull))
+#define _sz_shift_low(x) (x)
+#define _sz_shift_high(x) ((x + 77ull) & 0xFFull)
+#define _sz_prime_mod(x) (x % SZ_U64_MAX_PRIME)
+
+SZ_PUBLIC sz_u64_t sz_hash_serial(sz_cptr_t start, sz_size_t length) {
+
+    sz_u64_t hash_low = 0;
+    sz_u64_t hash_high = 0;
+    sz_u8_t const *text = (sz_u8_t const *)start;
+    sz_u8_t const *text_end = text + length;
+
+    switch (length) {
+    case 0: return 0;
+
+    // Texts under 7 bytes long are definitely below the largest prime.
+    case 1:
+        hash_low = _sz_shift_low(text[0]);
+        hash_high = _sz_shift_high(text[0]);
+        break;
+    case 2:
+        hash_low = _sz_shift_low(text[0]) * 31ull + _sz_shift_low(text[1]);
+        hash_high = _sz_shift_high(text[0]) * 257ull + _sz_shift_high(text[1]);
+        break;
+    case 3:
+        hash_low = _sz_shift_low(text[0]) * 31ull * 31ull + //
+                   _sz_shift_low(text[1]) * 31ull +         //
+                   _sz_shift_low(text[2]);
+        hash_high = _sz_shift_high(text[0]) * 257ull * 257ull + //
+                    _sz_shift_high(text[1]) * 257ull +          //
+                    _sz_shift_high(text[2]);
+        break;
+    case 4:
+        hash_low = _sz_shift_low(text[0]) * 31ull * 31ull * 31ull + //
+                   _sz_shift_low(text[1]) * 31ull * 31ull +         //
+                   _sz_shift_low(text[2]) * 31ull +                 //
+                   _sz_shift_low(text[3]);
+        hash_high = _sz_shift_high(text[0]) * 257ull * 257ull * 257ull + //
+                    _sz_shift_high(text[1]) * 257ull * 257ull +          //
+                    _sz_shift_high(text[2]) * 257ull +                   //
+                    _sz_shift_high(text[3]);
+        break;
+    case 5:
+        hash_low = _sz_shift_low(text[0]) * 31ull * 31ull * 31ull * 31ull + //
+                   _sz_shift_low(text[1]) * 31ull * 31ull * 31ull +         //
+                   _sz_shift_low(text[2]) * 31ull * 31ull +                 //
+                   _sz_shift_low(text[3]) * 31ull +                         //
+                   _sz_shift_low(text[4]);
+        hash_high = _sz_shift_high(text[0]) * 257ull * 257ull * 257ull * 257ull + //
+                    _sz_shift_high(text[1]) * 257ull * 257ull * 257ull +          //
+                    _sz_shift_high(text[2]) * 257ull * 257ull +                   //
+                    _sz_shift_high(text[3]) * 257ull +                            //
+                    _sz_shift_high(text[4]);
+        break;
+    case 6:
+        hash_low = _sz_shift_low(text[0]) * 31ull * 31ull * 31ull * 31ull * 31ull + //
+                   _sz_shift_low(text[1]) * 31ull * 31ull * 31ull * 31ull +         //
+                   _sz_shift_low(text[2]) * 31ull * 31ull * 31ull +                 //
+                   _sz_shift_low(text[3]) * 31ull * 31ull +                         //
+                   _sz_shift_low(text[4]) * 31ull +                                 //
+                   _sz_shift_low(text[5]);
+        hash_high = _sz_shift_high(text[0]) * 257ull * 257ull * 257ull * 257ull * 257ull + //
+                    _sz_shift_high(text[1]) * 257ull * 257ull * 257ull * 257ull +          //
+                    _sz_shift_high(text[2]) * 257ull * 257ull * 257ull +                   //
+                    _sz_shift_high(text[3]) * 257ull * 257ull +                            //
+                    _sz_shift_high(text[4]) * 257ull +                                     //
+                    _sz_shift_high(text[5]);
+        break;
+    case 7:
+        hash_low = _sz_shift_low(text[0]) * 31ull * 31ull * 31ull * 31ull * 31ull * 31ull + //
+                   _sz_shift_low(text[1]) * 31ull * 31ull * 31ull * 31ull * 31ull +         //
+                   _sz_shift_low(text[2]) * 31ull * 31ull * 31ull * 31ull +                 //
+                   _sz_shift_low(text[3]) * 31ull * 31ull * 31ull +                         //
+                   _sz_shift_low(text[4]) * 31ull * 31ull +                                 //
+                   _sz_shift_low(text[5]) * 31ull +                                         //
+                   _sz_shift_low(text[6]);
+        hash_high = _sz_shift_high(text[0]) * 257ull * 257ull * 257ull * 257ull * 257ull * 257ull + //
+                    _sz_shift_high(text[1]) * 257ull * 257ull * 257ull * 257ull * 257ull +          //
+                    _sz_shift_high(text[2]) * 257ull * 257ull * 257ull * 257ull +                   //
+                    _sz_shift_high(text[3]) * 257ull * 257ull * 257ull +                            //
+                    _sz_shift_high(text[4]) * 257ull * 257ull +                                     //
+                    _sz_shift_high(text[5]) * 257ull +                                              //
+                    _sz_shift_high(text[6]);
+        break;
+    default:
+        // Unroll the first seven cycles:
+        hash_low = hash_low * 31ull + _sz_shift_low(text[0]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[0]);
+        hash_low = hash_low * 31ull + _sz_shift_low(text[1]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[1]);
+        hash_low = hash_low * 31ull + _sz_shift_low(text[2]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[2]);
+        hash_low = hash_low * 31ull + _sz_shift_low(text[3]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[3]);
+        hash_low = hash_low * 31ull + _sz_shift_low(text[4]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[4]);
+        hash_low = hash_low * 31ull + _sz_shift_low(text[5]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[5]);
+        hash_low = hash_low * 31ull + _sz_shift_low(text[6]);
+        hash_high = hash_high * 257ull + _sz_shift_high(text[6]);
+        text += 7;
+
+        // Iterate throw the rest with the modulus:
+        for (; text != text_end; ++text) {
+            hash_low = hash_low * 31ull + _sz_shift_low(text[0]);
+            hash_high = hash_high * 257ull + _sz_shift_high(text[0]);
+            // Wrap the hashes around:
+            hash_low = _sz_prime_mod(hash_low);
+            hash_high = _sz_prime_mod(hash_high);
+        }
+        break;
+    }
+
+    return _sz_hash_mix(hash_low, hash_high);
+}
+
+SZ_PUBLIC void sz_hashes_serial(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                                sz_hash_callback_t callback, void *callback_handle) {
+
+    if (length < window_length || !window_length) return;
+    sz_u8_t const *text = (sz_u8_t const *)start;
+    sz_u8_t const *text_end = text + length;
+
+    // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
+    sz_u64_t prime_power_low = 1, prime_power_high = 1;
+    for (sz_size_t i = 0; i + 1 < window_length; ++i)
+        prime_power_low = (prime_power_low * 31ull) % SZ_U64_MAX_PRIME,
+        prime_power_high = (prime_power_high * 257ull) % SZ_U64_MAX_PRIME;
+
+    // Compute the initial hash value for the first window.
+    sz_u64_t hash_low = 0, hash_high = 0, hash_mix;
+    for (sz_u8_t const *first_end = text + window_length; text < first_end; ++text)
+        hash_low = (hash_low * 31ull + _sz_shift_low(*text)) % SZ_U64_MAX_PRIME,
+        hash_high = (hash_high * 257ull + _sz_shift_high(*text)) % SZ_U64_MAX_PRIME;
+
+    // In most cases the fingerprint length will be a power of two.
+    hash_mix = _sz_hash_mix(hash_low, hash_high);
+    callback((sz_cptr_t)text, window_length, hash_mix, callback_handle);
+
+    // Compute the hash value for every window, exporting into the fingerprint,
+    // using the expensive modulo operation.
+    sz_size_t cycles = 1;
+    sz_size_t const step_mask = step - 1;
+    for (; text < text_end; ++text, ++cycles) {
+        // Discard one character:
+        hash_low -= _sz_shift_low(*(text - window_length)) * prime_power_low;
+        hash_high -= _sz_shift_high(*(text - window_length)) * prime_power_high;
+        // And add a new one:
+        hash_low = 31ull * hash_low + _sz_shift_low(*text);
+        hash_high = 257ull * hash_high + _sz_shift_high(*text);
+        // Wrap the hashes around:
+        hash_low = _sz_prime_mod(hash_low);
+        hash_high = _sz_prime_mod(hash_high);
+        // Mix only if we've skipped enough hashes.
+        if ((cycles & step_mask) == 0) {
+            hash_mix = _sz_hash_mix(hash_low, hash_high);
+            callback((sz_cptr_t)text, window_length, hash_mix, callback_handle);
+        }
+    }
+}
+
+#undef _sz_shift_low
+#undef _sz_shift_high
+#undef _sz_hash_mix
+#undef _sz_prime_mod
+
+/**
+ *  @brief  Uses a small lookup-table to convert a lowercase character to uppercase.
+ */
+SZ_INTERNAL sz_u8_t sz_u8_tolower(sz_u8_t c) {
+    static sz_u8_t const lowered[256] = {
+        0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  //
+        16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  //
+        32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  //
+        48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  //
+        64,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //
+        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91,  92,  93,  94,  95,  //
+        96,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //
+        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, //
+        128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, //
+        144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, //
+        160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, //
+        176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, //
+        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
+        240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, //
+        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
+        240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, //
+    };
+    return lowered[c];
+}
+
+/**
+ *  @brief  Uses a small lookup-table to convert an uppercase character to lowercase.
+ */
+SZ_INTERNAL sz_u8_t sz_u8_toupper(sz_u8_t c) {
+    static sz_u8_t const upped[256] = {
+        0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  //
+        16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  //
+        32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  //
+        48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  //
+        64,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //
+        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91,  92,  93,  94,  95,  //
+        96,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,  //
+        80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  123, 124, 125, 126, 127, //
+        128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, //
+        144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, //
+        160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, //
+        176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, //
+        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
+        240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, //
+        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
+        240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, //
+    };
+    return upped[c];
+}
+
+/**
+ *  @brief  Uses two small lookup tables (768 bytes total) to accelerate division by a small
+ *          unsigned integer. Performs two lookups, one multiplication, two shifts, and two accumulations.
+ *
+ *  @param  divisor Integral value larger than one.
+ *  @param  number  Integral value to divide.
+ */
+SZ_INTERNAL sz_u8_t sz_u8_divide(sz_u8_t number, sz_u8_t divisor) {
+    static sz_u16_t const multipliers[256] = {
+        0,     0,     0,     21846, 0,     39322, 21846, 9363,  0,     50973, 39322, 29790, 21846, 15124, 9363,  4370,
+        0,     57826, 50973, 44841, 39322, 34329, 29790, 25645, 21846, 18351, 15124, 12137, 9363,  6780,  4370,  2115,
+        0,     61565, 57826, 54302, 50973, 47824, 44841, 42011, 39322, 36765, 34329, 32006, 29790, 27671, 25645, 23705,
+        21846, 20063, 18351, 16706, 15124, 13602, 12137, 10725, 9363,  8049,  6780,  5554,  4370,  3224,  2115,  1041,
+        0,     63520, 61565, 59668, 57826, 56039, 54302, 52614, 50973, 49377, 47824, 46313, 44841, 43407, 42011, 40649,
+        39322, 38028, 36765, 35532, 34329, 33154, 32006, 30885, 29790, 28719, 27671, 26647, 25645, 24665, 23705, 22766,
+        21846, 20945, 20063, 19198, 18351, 17520, 16706, 15907, 15124, 14356, 13602, 12863, 12137, 11424, 10725, 10038,
+        9363,  8700,  8049,  7409,  6780,  6162,  5554,  4957,  4370,  3792,  3224,  2665,  2115,  1573,  1041,  517,
+        0,     64520, 63520, 62535, 61565, 60609, 59668, 58740, 57826, 56926, 56039, 55164, 54302, 53452, 52614, 51788,
+        50973, 50169, 49377, 48595, 47824, 47063, 46313, 45572, 44841, 44120, 43407, 42705, 42011, 41326, 40649, 39982,
+        39322, 38671, 38028, 37392, 36765, 36145, 35532, 34927, 34329, 33738, 33154, 32577, 32006, 31443, 30885, 30334,
+        29790, 29251, 28719, 28192, 27671, 27156, 26647, 26143, 25645, 25152, 24665, 24182, 23705, 23233, 22766, 22303,
+        21846, 21393, 20945, 20502, 20063, 19628, 19198, 18772, 18351, 17933, 17520, 17111, 16706, 16305, 15907, 15514,
+        15124, 14738, 14356, 13977, 13602, 13231, 12863, 12498, 12137, 11779, 11424, 11073, 10725, 10380, 10038, 9699,
+        9363,  9030,  8700,  8373,  8049,  7727,  7409,  7093,  6780,  6470,  6162,  5857,  5554,  5254,  4957,  4662,
+        4370,  4080,  3792,  3507,  3224,  2943,  2665,  2388,  2115,  1843,  1573,  1306,  1041,  778,   517,   258,
+    };
+    // This table can be avoided using a single addition and counting trailing zeros.
+    static sz_u8_t const shifts[256] = {
+        0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //
+        4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, //
+        5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, //
+        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, //
+        6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, //
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, //
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, //
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, //
+    };
+    sz_u32_t multiplier = multipliers[divisor];
+    sz_u8_t shift = shifts[divisor];
+
+    sz_u16_t q = (sz_u16_t)((multiplier * number) >> 16);
+    sz_u16_t t = ((number - q) >> 1) + q;
+    return (sz_u8_t)(t >> shift);
+}
+
+SZ_PUBLIC void sz_tolower_serial(sz_cptr_t text, sz_size_t length, sz_ptr_t result) {
+    sz_u8_t *unsigned_result = (sz_u8_t *)result;
+    sz_u8_t const *unsigned_text = (sz_u8_t const *)text;
+    sz_u8_t const *end = unsigned_text + length;
+    for (; unsigned_text != end; ++unsigned_text, ++unsigned_result) *unsigned_result = sz_u8_tolower(*unsigned_text);
+}
+
+SZ_PUBLIC void sz_toupper_serial(sz_cptr_t text, sz_size_t length, sz_ptr_t result) {
+    sz_u8_t *unsigned_result = (sz_u8_t *)result;
+    sz_u8_t const *unsigned_text = (sz_u8_t const *)text;
+    sz_u8_t const *end = unsigned_text + length;
+    for (; unsigned_text != end; ++unsigned_text, ++unsigned_result) *unsigned_result = sz_u8_toupper(*unsigned_text);
+}
+
+SZ_PUBLIC void sz_toascii_serial(sz_cptr_t text, sz_size_t length, sz_ptr_t result) {
+    sz_u8_t *unsigned_result = (sz_u8_t *)result;
+    sz_u8_t const *unsigned_text = (sz_u8_t const *)text;
+    sz_u8_t const *end = unsigned_text + length;
+    for (; unsigned_text != end; ++unsigned_text, ++unsigned_result) *unsigned_result = *unsigned_text & 0x7F;
+}
+
+SZ_PUBLIC void sz_generate(sz_cptr_t alphabet, sz_size_t alphabet_size, sz_ptr_t result, sz_size_t result_length,
+                           sz_random_generator_t generator, void *generator_user_data) {
+
+    sz_assert(alphabet_size > 0 && alphabet_size <= 256 && "Inadequate alphabet size");
+
+    if (alphabet_size == 1)
+        for (sz_cptr_t end = result + result_length; result != end; ++result) *result = *alphabet;
+
+    else {
+        sz_assert(generator && "Expects a valid random generator");
+        for (sz_cptr_t end = result + result_length; result != end; ++result)
+            *result = alphabet[sz_u8_divide(generator(generator_user_data) & 0xFF, (sz_u8_t)alphabet_size)];
+    }
+}
+
+#pragma endregion
+
+/*
+ *  Serial implementation of string class operations.
+ */
+#pragma region Serial Implementation for the String Class
+
+/**
+ *  @brief  Threshold for switching to SWAR (8-bytes at a time) backend over serial byte-level for-loops.
+ *          On very short strings, under 16 bytes long, at most a single word will be processed with SWAR.
+ *          Assuming potentially misaligned loads, SWAR makes sense only after ~24 bytes.
+ */
+#ifndef SZ_SWAR_THRESHOLD
+#define SZ_SWAR_THRESHOLD (24) // bytes
+#endif
+
+SZ_PUBLIC sz_bool_t sz_string_is_on_stack(sz_string_t const *string) {
+    // It doesn't matter if it's on stack or heap, the pointer location is the same.
+    return (sz_bool_t)((sz_cptr_t)string->internal.start == (sz_cptr_t)&string->internal.chars[0]);
+}
+
+SZ_PUBLIC void sz_string_range(sz_string_t const *string, sz_ptr_t *start, sz_size_t *length) {
+    sz_size_t is_small = (sz_cptr_t)string->internal.start == (sz_cptr_t)&string->internal.chars[0];
+    sz_size_t is_big_mask = is_small - 1ull;
+    *start = string->external.start; // It doesn't matter if it's on stack or heap, the pointer location is the same.
+    // If the string is small, use branch-less approach to mask-out the top 7 bytes of the length.
+    *length = string->external.length & (0x00000000000000FFull | is_big_mask);
+}
+
+SZ_PUBLIC void sz_string_unpack(sz_string_t const *string, sz_ptr_t *start, sz_size_t *length, sz_size_t *space,
+                                sz_bool_t *is_external) {
+    sz_size_t is_small = (sz_cptr_t)string->internal.start == (sz_cptr_t)&string->internal.chars[0];
+    sz_size_t is_big_mask = is_small - 1ull;
+    *start = string->external.start; // It doesn't matter if it's on stack or heap, the pointer location is the same.
+    // If the string is small, use branch-less approach to mask-out the top 7 bytes of the length.
+    *length = string->external.length & (0x00000000000000FFull | is_big_mask);
+    // In case the string is small, the `is_small - 1ull` will become 0xFFFFFFFFFFFFFFFFull.
+    *space = sz_u64_blend(SZ_STRING_INTERNAL_SPACE, string->external.space, is_big_mask);
+    *is_external = (sz_bool_t)!is_small;
+}
+
+SZ_PUBLIC sz_bool_t sz_string_equal(sz_string_t const *a, sz_string_t const *b) {
+    // Tempting to say that the external.length is bitwise the same even if it includes
+    // some bytes of the on-stack payload, but we don't at this writing maintain that invariant.
+    // (An on-stack string includes noise bytes in the high-order bits of external.length. So do this
+    // the hard/correct way.
+
+#if SZ_USE_MISALIGNED_LOADS
+    // Dealing with StringZilla strings, we know that the `start` pointer always points
+    // to a word at least 8 bytes long. Therefore, we can compare the first 8 bytes at once.
+
+#endif
+    // Alternatively, fall back to byte-by-byte comparison.
+    sz_ptr_t a_start, b_start;
+    sz_size_t a_length, b_length;
+    sz_string_range(a, &a_start, &a_length);
+    sz_string_range(b, &b_start, &b_length);
+    return (sz_bool_t)(a_length == b_length && sz_equal(a_start, b_start, b_length));
+}
+
+SZ_PUBLIC sz_ordering_t sz_string_order(sz_string_t const *a, sz_string_t const *b) {
+#if SZ_USE_MISALIGNED_LOADS
+    // Dealing with StringZilla strings, we know that the `start` pointer always points
+    // to a word at least 8 bytes long. Therefore, we can compare the first 8 bytes at once.
+
+#endif
+    // Alternatively, fall back to byte-by-byte comparison.
+    sz_ptr_t a_start, b_start;
+    sz_size_t a_length, b_length;
+    sz_string_range(a, &a_start, &a_length);
+    sz_string_range(b, &b_start, &b_length);
+    return sz_order(a_start, a_length, b_start, b_length);
+}
+
+SZ_PUBLIC void sz_string_init(sz_string_t *string) {
+    sz_assert(string && "String can't be SZ_NULL.");
+
+    // Only 8 + 1 + 1 need to be initialized.
+    string->internal.start = &string->internal.chars[0];
+    // But for safety let's initialize the entire structure to zeros.
+    // string->internal.chars[0] = 0;
+    // string->internal.length = 0;
+    string->u64s[1] = 0;
+    string->u64s[2] = 0;
+    string->u64s[3] = 0;
+}
+
+SZ_PUBLIC sz_ptr_t sz_string_init_length(sz_string_t *string, sz_size_t length, sz_memory_allocator_t *allocator) {
+    sz_size_t space_needed = length + 1; // space for trailing \0
+    sz_assert(string && allocator && "String and allocator can't be SZ_NULL.");
+    // Initialize the string to zeros for safety.
+    string->u64s[1] = 0;
+    string->u64s[2] = 0;
+    string->u64s[3] = 0;
+    // If we are lucky, no memory allocations will be needed.
+    if (space_needed <= SZ_STRING_INTERNAL_SPACE) {
+        string->internal.start = &string->internal.chars[0];
+        string->internal.length = (sz_u8_t)length;
+    }
+    else {
+        // If we are not lucky, we need to allocate memory.
+        string->external.start = (sz_ptr_t)allocator->allocate(space_needed, allocator->handle);
+        if (!string->external.start) return SZ_NULL;
+        string->external.length = length;
+        string->external.space = space_needed;
+    }
+    sz_assert(&string->internal.start == &string->external.start && "Alignment confusion");
+    string->external.start[length] = 0;
+    return string->external.start;
+}
+
+SZ_PUBLIC sz_ptr_t sz_string_reserve(sz_string_t *string, sz_size_t new_capacity, sz_memory_allocator_t *allocator) {
+
+    sz_assert(string && "String can't be SZ_NULL.");
+
+    sz_size_t new_space = new_capacity + 1;
+    if (new_space <= SZ_STRING_INTERNAL_SPACE) return string->external.start;
+
+    sz_ptr_t string_start;
+    sz_size_t string_length;
+    sz_size_t string_space;
+    sz_bool_t string_is_external;
+    sz_string_unpack(string, &string_start, &string_length, &string_space, &string_is_external);
+    sz_assert(new_space > string_space && "New space must be larger than current.");
+
+    sz_ptr_t new_start = (sz_ptr_t)allocator->allocate(new_space, allocator->handle);
+    if (!new_start) return SZ_NULL;
+
+    sz_copy(new_start, string_start, string_length);
+    string->external.start = new_start;
+    string->external.space = new_space;
+    string->external.padding = 0;
+    string->external.length = string_length;
+
+    // Deallocate the old string.
+    if (string_is_external) allocator->free(string_start, string_space, allocator->handle);
+    return string->external.start;
+}
+
+SZ_PUBLIC sz_ptr_t sz_string_expand(sz_string_t *string, sz_size_t offset, sz_size_t added_length,
+                                    sz_memory_allocator_t *allocator) {
+
+    sz_assert(string && allocator && "String and allocator can't be SZ_NULL.");
+
+    sz_ptr_t string_start;
+    sz_size_t string_length;
+    sz_size_t string_space;
+    sz_bool_t string_is_external;
+    sz_string_unpack(string, &string_start, &string_length, &string_space, &string_is_external);
+
+    // The user intended to extend the string.
+    offset = sz_min_of_two(offset, string_length);
+
+    // If we are lucky, no memory allocations will be needed.
+    if (offset + string_length + added_length < string_space) {
+        sz_move(string_start + offset + added_length, string_start + offset, string_length - offset);
+        string_start[string_length + added_length] = 0;
+        // Even if the string is on the stack, the `+=` won't affect the tail of the string.
+        string->external.length += added_length;
+    }
+    // If we are not lucky, we need to allocate more memory.
+    else {
+        sz_size_t next_planned_size = sz_max_of_two(SZ_CACHE_LINE_WIDTH, string_space * 2ull);
+        sz_size_t min_needed_space = sz_size_bit_ceil(offset + string_length + added_length + 1);
+        sz_size_t new_space = sz_max_of_two(min_needed_space, next_planned_size);
+        string_start = sz_string_reserve(string, new_space - 1, allocator);
+        if (!string_start) return SZ_NULL;
+
+        // Copy into the new buffer.
+        sz_move(string_start + offset + added_length, string_start + offset, string_length - offset);
+        string_start[string_length + added_length] = 0;
+        string->external.length = string_length + added_length;
+    }
+
+    return string_start;
+}
+
+SZ_PUBLIC sz_size_t sz_string_erase(sz_string_t *string, sz_size_t offset, sz_size_t length) {
+
+    sz_assert(string && "String can't be SZ_NULL.");
+
+    sz_ptr_t string_start;
+    sz_size_t string_length;
+    sz_size_t string_space;
+    sz_bool_t string_is_external;
+    sz_string_unpack(string, &string_start, &string_length, &string_space, &string_is_external);
+
+    // Normalize the offset, it can't be larger than the length.
+    offset = sz_min_of_two(offset, string_length);
+
+    // We shouldn't normalize the length, to avoid overflowing on `offset + length >= string_length`,
+    // if receiving `length == SZ_SIZE_MAX`. After following expression the `length` will contain
+    // exactly the delta between original and final length of this `string`.
+    length = sz_min_of_two(length, string_length - offset);
+
+    // There are 2 common cases, that wouldn't even require a `memmove`:
+    //      1.  Erasing the entire contents of the string.
+    //          In that case `length` argument will be equal or greater than `length` member.
+    //      2.  Removing the tail of the string with something like `string.pop_back()` in C++.
+    //
+    // In both of those, regardless of the location of the string - stack or heap,
+    // the erasing is as easy as setting the length to the offset.
+    // In every other case, we must `memmove` the tail of the string to the left.
+    if (offset + length < string_length)
+        sz_move(string_start + offset, string_start + offset + length, string_length - offset - length);
+
+    // The `string->external.length = offset` assignment would discard last characters
+    // of the on-the-stack string, but inplace subtraction would work.
+    string->external.length -= length;
+    string_start[string_length - length] = 0;
+    return length;
+}
+
+SZ_PUBLIC void sz_string_free(sz_string_t *string, sz_memory_allocator_t *allocator) {
+    if (!sz_string_is_on_stack(string))
+        allocator->free(string->external.start, string->external.space, allocator->handle);
+    sz_string_init(string);
+}
+
+SZ_PUBLIC void sz_fill_serial(sz_ptr_t target, sz_size_t length, sz_u8_t value) {
+    sz_ptr_t end = target + length;
+    // Dealing with short strings, a single sequential pass would be faster.
+    // If the size is larger than 2 words, then at least 1 of them will be aligned.
+    // But just one aligned word may not be worth SWAR.
+    if (length < SZ_SWAR_THRESHOLD)
+        while (target != end) *(target++) = value;
+
+    // In case of long strings, skip unaligned bytes, and then fill the rest in 64-bit chunks.
+    else {
+        sz_u64_t value64 = (sz_u64_t)(value) * 0x0101010101010101ull;
+        while ((sz_size_t)target & 7ull) *(target++) = value;
+        while (target + 8 <= end) *(sz_u64_t *)target = value64, target += 8;
+        while (target != end) *(target++) = value;
+    }
+}
+
+SZ_PUBLIC void sz_copy_serial(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+#if SZ_USE_MISALIGNED_LOADS
+    while (length >= 8) *(sz_u64_t *)target = *(sz_u64_t *)source, target += 8, source += 8, length -= 8;
+#endif
+    while (length--) *(target++) = *(source++);
+}
+
+SZ_PUBLIC void sz_move_serial(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    // Implementing `memmove` is trickier, than `memcpy`, as the ranges may overlap.
+    // Existing implementations often have two passes, in normal and reversed order,
+    // depending on the relation of `target` and `source` addresses.
+    // https://student.cs.uwaterloo.ca/~cs350/common/os161-src-html/doxygen/html/memmove_8c_source.html
+    // https://marmota.medium.com/c-language-making-memmove-def8792bb8d5
+    //
+    // We can use the `memcpy` like left-to-right pass if we know that the `target` is before `source`.
+    // Or if we know that they don't intersect! In that case the traversal order is irrelevant,
+    // but older CPUs may predict and fetch forward-passes better.
+    if (target < source || target >= source + length) {
+#if SZ_USE_MISALIGNED_LOADS
+        while (length >= 8) *(sz_u64_t *)target = *(sz_u64_t *)source, target += 8, source += 8, length -= 8;
+#endif
+        while (length--) *(target++) = *(source++);
+    }
+    else {
+        // Jump to the end and walk backwards.
+        target += length, source += length;
+#if SZ_USE_MISALIGNED_LOADS
+        while (length >= 8) *(sz_u64_t *)(target -= 8) = *(sz_u64_t *)(source -= 8), length -= 8;
+#endif
+        while (length--) *(--target) = *(--source);
+    }
+}
+
+#pragma endregion
+
+/*
+ *  @brief  Serial implementation for strings sequence processing.
+ */
+#pragma region Serial Implementation for Sequences
+
+SZ_PUBLIC sz_size_t sz_partition(sz_sequence_t *sequence, sz_sequence_predicate_t predicate) {
+
+    sz_size_t matches = 0;
+    while (matches != sequence->count && predicate(sequence, sequence->order[matches])) ++matches;
+
+    for (sz_size_t i = matches + 1; i < sequence->count; ++i)
+        if (predicate(sequence, sequence->order[i]))
+            sz_u64_swap(sequence->order + i, sequence->order + matches), ++matches;
+
+    return matches;
+}
+
+SZ_PUBLIC void sz_merge(sz_sequence_t *sequence, sz_size_t partition, sz_sequence_comparator_t less) {
+
+    sz_size_t start_b = partition + 1;
+
+    // If the direct merge is already sorted
+    if (!less(sequence, sequence->order[start_b], sequence->order[partition])) return;
+
+    sz_size_t start_a = 0;
+    while (start_a <= partition && start_b <= sequence->count) {
+
+        // If element 1 is in right place
+        if (!less(sequence, sequence->order[start_b], sequence->order[start_a])) { start_a++; }
+        else {
+            sz_size_t value = sequence->order[start_b];
+            sz_size_t index = start_b;
+
+            // Shift all the elements between element 1
+            // element 2, right by 1.
+            while (index != start_a) { sequence->order[index] = sequence->order[index - 1], index--; }
+            sequence->order[start_a] = value;
+
+            // Update all the pointers
+            start_a++;
+            partition++;
+            start_b++;
+        }
+    }
+}
+
+SZ_PUBLIC void sz_sort_insertion(sz_sequence_t *sequence, sz_sequence_comparator_t less) {
+    sz_u64_t *keys = sequence->order;
+    sz_size_t keys_count = sequence->count;
+    for (sz_size_t i = 1; i < keys_count; i++) {
+        sz_u64_t i_key = keys[i];
+        sz_size_t j = i;
+        for (; j > 0 && less(sequence, i_key, keys[j - 1]); --j) keys[j] = keys[j - 1];
+        keys[j] = i_key;
+    }
+}
+
+SZ_INTERNAL void _sz_sift_down(sz_sequence_t *sequence, sz_sequence_comparator_t less, sz_u64_t *order, sz_size_t start,
+                               sz_size_t end) {
+    sz_size_t root = start;
+    while (2 * root + 1 <= end) {
+        sz_size_t child = 2 * root + 1;
+        if (child + 1 <= end && less(sequence, order[child], order[child + 1])) { child++; }
+        if (!less(sequence, order[root], order[child])) { return; }
+        sz_u64_swap(order + root, order + child);
+        root = child;
+    }
+}
+
+SZ_INTERNAL void _sz_heapify(sz_sequence_t *sequence, sz_sequence_comparator_t less, sz_u64_t *order, sz_size_t count) {
+    sz_size_t start = (count - 2) / 2;
+    while (1) {
+        _sz_sift_down(sequence, less, order, start, count - 1);
+        if (start == 0) return;
+        start--;
+    }
+}
+
+SZ_INTERNAL void _sz_heapsort(sz_sequence_t *sequence, sz_sequence_comparator_t less, sz_size_t first, sz_size_t last) {
+    sz_u64_t *order = sequence->order;
+    sz_size_t count = last - first;
+    _sz_heapify(sequence, less, order + first, count);
+    sz_size_t end = count - 1;
+    while (end > 0) {
+        sz_u64_swap(order + first, order + first + end);
+        end--;
+        _sz_sift_down(sequence, less, order + first, 0, end);
+    }
+}
+
+SZ_PUBLIC void sz_sort_introsort_recursion(sz_sequence_t *sequence, sz_sequence_comparator_t less, sz_size_t first,
+                                           sz_size_t last, sz_size_t depth) {
+
+    sz_size_t length = last - first;
+    switch (length) {
+    case 0:
+    case 1: return;
+    case 2:
+        if (less(sequence, sequence->order[first + 1], sequence->order[first]))
+            sz_u64_swap(&sequence->order[first], &sequence->order[first + 1]);
+        return;
+    case 3: {
+        sz_u64_t a = sequence->order[first];
+        sz_u64_t b = sequence->order[first + 1];
+        sz_u64_t c = sequence->order[first + 2];
+        if (less(sequence, b, a)) sz_u64_swap(&a, &b);
+        if (less(sequence, c, b)) sz_u64_swap(&c, &b);
+        if (less(sequence, b, a)) sz_u64_swap(&a, &b);
+        sequence->order[first] = a;
+        sequence->order[first + 1] = b;
+        sequence->order[first + 2] = c;
+        return;
+    }
+    }
+    // Until a certain length, the quadratic-complexity insertion-sort is fine
+    if (length <= 16) {
+        sz_sequence_t sub_seq = *sequence;
+        sub_seq.order += first;
+        sub_seq.count = length;
+        sz_sort_insertion(&sub_seq, less);
+        return;
+    }
+
+    // Fallback to N-logN-complexity heap-sort
+    if (depth == 0) {
+        _sz_heapsort(sequence, less, first, last);
+        return;
+    }
+
+    --depth;
+
+    // Median-of-three logic to choose pivot
+    sz_size_t median = first + length / 2;
+    if (less(sequence, sequence->order[median], sequence->order[first]))
+        sz_u64_swap(&sequence->order[first], &sequence->order[median]);
+    if (less(sequence, sequence->order[last - 1], sequence->order[first]))
+        sz_u64_swap(&sequence->order[first], &sequence->order[last - 1]);
+    if (less(sequence, sequence->order[median], sequence->order[last - 1]))
+        sz_u64_swap(&sequence->order[median], &sequence->order[last - 1]);
+
+    // Partition using the median-of-three as the pivot
+    sz_u64_t pivot = sequence->order[median];
+    sz_size_t left = first;
+    sz_size_t right = last - 1;
+    while (1) {
+        while (less(sequence, sequence->order[left], pivot)) left++;
+        while (less(sequence, pivot, sequence->order[right])) right--;
+        if (left >= right) break;
+        sz_u64_swap(&sequence->order[left], &sequence->order[right]);
+        left++;
+        right--;
+    }
+
+    // Recursively sort the partitions
+    sz_sort_introsort_recursion(sequence, less, first, left, depth);
+    sz_sort_introsort_recursion(sequence, less, right + 1, last, depth);
+}
+
+SZ_PUBLIC void sz_sort_introsort(sz_sequence_t *sequence, sz_sequence_comparator_t less) {
+    if (sequence->count == 0) return;
+    sz_size_t size_is_not_power_of_two = (sequence->count & (sequence->count - 1)) != 0;
+    sz_size_t depth_limit = sz_size_log2i_nonzero(sequence->count) + size_is_not_power_of_two;
+    sz_sort_introsort_recursion(sequence, less, 0, sequence->count, depth_limit);
+}
+
+SZ_PUBLIC void sz_sort_recursion( //
+    sz_sequence_t *sequence, sz_size_t bit_idx, sz_size_t bit_max, sz_sequence_comparator_t comparator,
+    sz_size_t partial_order_length) {
+
+    if (!sequence->count) return;
+
+    // Array of size one doesn't need sorting - only needs the prefix to be discarded.
+    if (sequence->count == 1) {
+        sz_u32_t *order_half_words = (sz_u32_t *)sequence->order;
+        order_half_words[1] = 0;
+        return;
+    }
+
+    // Partition a range of integers according to a specific bit value
+    sz_size_t split = 0;
+    sz_u64_t mask = (1ull << 63) >> bit_idx;
+
+    // The clean approach would be to perform a single pass over the sequence.
+    //
+    //    while (split != sequence->count && !(sequence->order[split] & mask)) ++split;
+    //    for (sz_size_t i = split + 1; i < sequence->count; ++i)
+    //        if (!(sequence->order[i] & mask)) sz_u64_swap(sequence->order + i, sequence->order + split), ++split;
+    //
+    // This, however, doesn't take into account the high relative cost of writes and swaps.
+    // To cercumvent that, we can first count the total number entries to be mapped into either part.
+    // And then walk through both parts, swapping the entries that are in the wrong part.
+    // This would often lead to ~15% performance gain.
+    sz_size_t count_with_bit_set = 0;
+    for (sz_size_t i = 0; i != sequence->count; ++i) count_with_bit_set += (sequence->order[i] & mask) != 0;
+    split = sequence->count - count_with_bit_set;
+
+    // It's possible that the sequence is already partitioned.
+    if (split != 0 && split != sequence->count) {
+        // Use two pointers to efficiently reposition elements.
+        // On pointer walks left-to-right from the start, and the other walks right-to-left from the end.
+        sz_size_t left = 0;
+        sz_size_t right = sequence->count - 1;
+        while (1) {
+            // Find the next element with the bit set on the left side.
+            while (left < split && !(sequence->order[left] & mask)) ++left;
+            // Find the next element without the bit set on the right side.
+            while (right >= split && (sequence->order[right] & mask)) --right;
+            // Swap the mispositioned elements.
+            if (left < split && right >= split) {
+                sz_u64_swap(sequence->order + left, sequence->order + right);
+                ++left;
+                --right;
+            }
+            else { break; }
+        }
+    }
+
+    // Go down recursively.
+    if (bit_idx < bit_max) {
+        sz_sequence_t a = *sequence;
+        a.count = split;
+        sz_sort_recursion(&a, bit_idx + 1, bit_max, comparator, partial_order_length);
+
+        sz_sequence_t b = *sequence;
+        b.order += split;
+        b.count -= split;
+        sz_sort_recursion(&b, bit_idx + 1, bit_max, comparator, partial_order_length);
+    }
+    // Reached the end of recursion.
+    else {
+        // Discard the prefixes.
+        sz_u32_t *order_half_words = (sz_u32_t *)sequence->order;
+        for (sz_size_t i = 0; i != sequence->count; ++i) { order_half_words[i * 2 + 1] = 0; }
+
+        sz_sequence_t a = *sequence;
+        a.count = split;
+        sz_sort_introsort(&a, comparator);
+
+        sz_sequence_t b = *sequence;
+        b.order += split;
+        b.count -= split;
+        sz_sort_introsort(&b, comparator);
+    }
+}
+
+SZ_INTERNAL sz_bool_t _sz_sort_is_less(sz_sequence_t *sequence, sz_size_t i_key, sz_size_t j_key) {
+    sz_cptr_t i_str = sequence->get_start(sequence, i_key);
+    sz_cptr_t j_str = sequence->get_start(sequence, j_key);
+    sz_size_t i_len = sequence->get_length(sequence, i_key);
+    sz_size_t j_len = sequence->get_length(sequence, j_key);
+    return (sz_bool_t)(sz_order_serial(i_str, i_len, j_str, j_len) == sz_less_k);
+}
+
+SZ_PUBLIC void sz_sort_partial(sz_sequence_t *sequence, sz_size_t partial_order_length) {
+
+    // Export up to 4 bytes into the `sequence` bits themselves
+    for (sz_size_t i = 0; i != sequence->count; ++i) {
+        sz_cptr_t begin = sequence->get_start(sequence, sequence->order[i]);
+        sz_size_t length = sequence->get_length(sequence, sequence->order[i]);
+        length = length > 4ull ? 4ull : length;
+        sz_ptr_t prefix = (sz_ptr_t)&sequence->order[i];
+        for (sz_size_t j = 0; j != length; ++j) prefix[7 - j] = begin[j];
+    }
+
+    // Perform optionally-parallel radix sort on them
+    sz_sort_recursion(sequence, 0, 32, (sz_sequence_comparator_t)_sz_sort_is_less, partial_order_length);
+}
+
+SZ_PUBLIC void sz_sort(sz_sequence_t *sequence) { sz_sort_partial(sequence, sequence->count); }
+
+#pragma endregion
+
+/*
+ *  @brief  AVX2 implementation of the string search algorithms.
+ *          Very minimalistic, but still faster than the serial implementation.
+ */
+#pragma region AVX2 Implementation
+
+#if SZ_USE_X86_AVX2
+#pragma GCC push_options
+#pragma GCC target("avx2")
+#pragma clang attribute push(__attribute__((target("avx2"))), apply_to = function)
+#include <immintrin.h>
+
+/**
+ *  @brief  Helper structure to simplify work with 256-bit registers.
+ */
+typedef union sz_u256_vec_t {
+    __m256i ymm;
+    __m128i xmms[2];
+    sz_u64_t u64s[4];
+    sz_u32_t u32s[8];
+    sz_u16_t u16s[16];
+    sz_u8_t u8s[32];
+} sz_u256_vec_t;
+
+SZ_PUBLIC void sz_fill_avx2(sz_ptr_t target, sz_size_t length, sz_u8_t value) {
+    for (; length >= 32; target += 32, length -= 32) _mm256_storeu_si256((__m256i *)target, _mm256_set1_epi8(value));
+    sz_fill_serial(target, length, value);
+}
+
+SZ_PUBLIC void sz_copy_avx2(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    for (; length >= 32; target += 32, source += 32, length -= 32)
+        _mm256_storeu_si256((__m256i *)target, _mm256_lddqu_si256((__m256i const *)source));
+    sz_copy_serial(target, source, length);
+}
+
+SZ_PUBLIC void sz_move_avx2(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    if (target < source || target >= source + length) {
+        for (; length >= 32; target += 32, source += 32, length -= 32)
+            _mm256_storeu_si256((__m256i *)target, _mm256_lddqu_si256((__m256i const *)source));
+        while (length--) *(target++) = *(source++);
+    }
+    else {
+        // Jump to the end and walk backwards.
+        for (target += length, source += length; length >= 32; length -= 32)
+            _mm256_storeu_si256((__m256i *)(target -= 32), _mm256_lddqu_si256((__m256i const *)(source -= 32)));
+        while (length--) *(--target) = *(--source);
+    }
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_byte_avx2(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+    int mask;
+    sz_u256_vec_t h_vec, n_vec;
+    n_vec.ymm = _mm256_set1_epi8(n[0]);
+
+    while (h_length >= 32) {
+        h_vec.ymm = _mm256_lddqu_si256((__m256i const *)h);
+        mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_vec.ymm, n_vec.ymm));
+        if (mask) return h + sz_u32_ctz(mask);
+        h += 32, h_length -= 32;
+    }
+
+    return sz_find_byte_serial(h, h_length, n);
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_avx2(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+    int mask;
+    sz_u256_vec_t h_vec, n_vec;
+    n_vec.ymm = _mm256_set1_epi8(n[0]);
+
+    while (h_length >= 32) {
+        h_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h + h_length - 32));
+        mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_vec.ymm, n_vec.ymm));
+        if (mask) return h + h_length - 1 - sz_u32_clz(mask);
+        h_length -= 32;
+    }
+
+    return sz_rfind_byte_serial(h, h_length, n);
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_avx2(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+    if (n_length == 1) return sz_find_byte_avx2(h, h_length, n);
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into YMM registers.
+    int matches;
+    sz_u256_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec;
+    n_first_vec.ymm = _mm256_set1_epi8(n[offset_first]);
+    n_mid_vec.ymm = _mm256_set1_epi8(n[offset_mid]);
+    n_last_vec.ymm = _mm256_set1_epi8(n[offset_last]);
+
+    // Scan through the string.
+    for (; h_length >= n_length + 32; h += 32, h_length -= 32) {
+        h_first_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h + offset_first));
+        h_mid_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h + offset_mid));
+        h_last_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h + offset_last));
+        matches = _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_first_vec.ymm, n_first_vec.ymm)) &
+                  _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_mid_vec.ymm, n_mid_vec.ymm)) &
+                  _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_last_vec.ymm, n_last_vec.ymm));
+        while (matches) {
+            int potential_offset = sz_u32_ctz(matches);
+            if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
+            matches &= matches - 1;
+        }
+    }
+
+    return sz_find_serial(h, h_length, n, n_length);
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_avx2(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+    if (n_length == 1) return sz_rfind_byte_avx2(h, h_length, n);
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into YMM registers.
+    int matches;
+    sz_u256_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec;
+    n_first_vec.ymm = _mm256_set1_epi8(n[offset_first]);
+    n_mid_vec.ymm = _mm256_set1_epi8(n[offset_mid]);
+    n_last_vec.ymm = _mm256_set1_epi8(n[offset_last]);
+
+    // Scan through the string.
+    sz_cptr_t h_reversed;
+    for (; h_length >= n_length + 32; h_length -= 32) {
+        h_reversed = h + h_length - n_length - 32 + 1;
+        h_first_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h_reversed + offset_first));
+        h_mid_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h_reversed + offset_mid));
+        h_last_vec.ymm = _mm256_lddqu_si256((__m256i const *)(h_reversed + offset_last));
+        matches = _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_first_vec.ymm, n_first_vec.ymm)) &
+                  _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_mid_vec.ymm, n_mid_vec.ymm)) &
+                  _mm256_movemask_epi8(_mm256_cmpeq_epi8(h_last_vec.ymm, n_last_vec.ymm));
+        while (matches) {
+            int potential_offset = sz_u32_clz(matches);
+            if (sz_equal(h + h_length - n_length - potential_offset, n, n_length))
+                return h + h_length - n_length - potential_offset;
+            matches &= ~(1 << (31 - potential_offset));
+        }
+    }
+
+    return sz_rfind_serial(h, h_length, n, n_length);
+}
+
+/**
+ *  @brief  There is no AVX2 instruction for fast multiplication of 64-bit integers.
+ *          This implementation is coming from Agner Fog's Vector Class Library.
+ */
+SZ_INTERNAL __m256i _mm256_mul_epu64(__m256i a, __m256i b) {
+    __m256i bswap = _mm256_shuffle_epi32(b, 0xB1);
+    __m256i prodlh = _mm256_mullo_epi32(a, bswap);
+    __m256i zero = _mm256_setzero_si256();
+    __m256i prodlh2 = _mm256_hadd_epi32(prodlh, zero);
+    __m256i prodlh3 = _mm256_shuffle_epi32(prodlh2, 0x73);
+    __m256i prodll = _mm256_mul_epu32(a, b);
+    __m256i prod = _mm256_add_epi64(prodll, prodlh3);
+    return prod;
+}
+
+SZ_PUBLIC void sz_hashes_avx2(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                              sz_hash_callback_t callback, void *callback_handle) {
+
+    if (length < window_length || !window_length) return;
+    if (length < 4 * window_length) {
+        sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
+        return;
+    }
+
+    // Using AVX2, we can perform 4 long integer multiplications and additions within one register.
+    // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
+    sz_size_t const max_hashes = length - window_length + 1;
+    sz_size_t const min_hashes_per_thread = max_hashes / 4; // At most one sequence can overlap between 2 threads.
+    sz_u8_t const *text_first = (sz_u8_t const *)start;
+    sz_u8_t const *text_second = text_first + min_hashes_per_thread;
+    sz_u8_t const *text_third = text_first + min_hashes_per_thread * 2;
+    sz_u8_t const *text_fourth = text_first + min_hashes_per_thread * 3;
+    sz_u8_t const *text_end = text_first + length;
+
+    // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
+    sz_u64_t prime_power_low = 1, prime_power_high = 1;
+    for (sz_size_t i = 0; i + 1 < window_length; ++i)
+        prime_power_low = (prime_power_low * 31ull) % SZ_U64_MAX_PRIME,
+        prime_power_high = (prime_power_high * 257ull) % SZ_U64_MAX_PRIME;
+
+    // Broadcast the constants into the registers.
+    sz_u256_vec_t prime_vec, golden_ratio_vec;
+    sz_u256_vec_t base_low_vec, base_high_vec, prime_power_low_vec, prime_power_high_vec, shift_high_vec;
+    base_low_vec.ymm = _mm256_set1_epi64x(31ull);
+    base_high_vec.ymm = _mm256_set1_epi64x(257ull);
+    shift_high_vec.ymm = _mm256_set1_epi64x(77ull);
+    prime_vec.ymm = _mm256_set1_epi64x(SZ_U64_MAX_PRIME);
+    golden_ratio_vec.ymm = _mm256_set1_epi64x(11400714819323198485ull);
+    prime_power_low_vec.ymm = _mm256_set1_epi64x(prime_power_low);
+    prime_power_high_vec.ymm = _mm256_set1_epi64x(prime_power_high);
+
+    // Compute the initial hash values for every one of the four windows.
+    sz_u256_vec_t hash_low_vec, hash_high_vec, hash_mix_vec, chars_low_vec, chars_high_vec;
+    hash_low_vec.ymm = _mm256_setzero_si256();
+    hash_high_vec.ymm = _mm256_setzero_si256();
+    for (sz_u8_t const *prefix_end = text_first + window_length; text_first < prefix_end;
+         ++text_first, ++text_second, ++text_third, ++text_fourth) {
+
+        // 1. Multiply the hashes by the base.
+        hash_low_vec.ymm = _mm256_mul_epu64(hash_low_vec.ymm, base_low_vec.ymm);
+        hash_high_vec.ymm = _mm256_mul_epu64(hash_high_vec.ymm, base_high_vec.ymm);
+
+        // 2. Load the four characters from `text_first`, `text_first + max_hashes_per_thread`,
+        //   `text_first + max_hashes_per_thread * 2`, `text_first + max_hashes_per_thread * 3`.
+        chars_low_vec.ymm = _mm256_set_epi64x(text_fourth[0], text_third[0], text_second[0], text_first[0]);
+        chars_high_vec.ymm = _mm256_add_epi8(chars_low_vec.ymm, shift_high_vec.ymm);
+
+        // 3. Add the incoming characters.
+        hash_low_vec.ymm = _mm256_add_epi64(hash_low_vec.ymm, chars_low_vec.ymm);
+        hash_high_vec.ymm = _mm256_add_epi64(hash_high_vec.ymm, chars_high_vec.ymm);
+
+        // 4. Compute the modulo. Assuming there are only 59 values between our prime
+        //    and the 2^64 value, we can simply compute the modulo by conditionally subtracting the prime.
+        hash_low_vec.ymm = _mm256_blendv_epi8(hash_low_vec.ymm, _mm256_sub_epi64(hash_low_vec.ymm, prime_vec.ymm),
+                                              _mm256_cmpgt_epi64(hash_low_vec.ymm, prime_vec.ymm));
+        hash_high_vec.ymm = _mm256_blendv_epi8(hash_high_vec.ymm, _mm256_sub_epi64(hash_high_vec.ymm, prime_vec.ymm),
+                                               _mm256_cmpgt_epi64(hash_high_vec.ymm, prime_vec.ymm));
+    }
+
+    // 5. Compute the hash mix, that will be used to index into the fingerprint.
+    //    This includes a serial step at the end.
+    hash_low_vec.ymm = _mm256_mul_epu64(hash_low_vec.ymm, golden_ratio_vec.ymm);
+    hash_high_vec.ymm = _mm256_mul_epu64(hash_high_vec.ymm, golden_ratio_vec.ymm);
+    hash_mix_vec.ymm = _mm256_xor_si256(hash_low_vec.ymm, hash_high_vec.ymm);
+    callback((sz_cptr_t)text_first, window_length, hash_mix_vec.u64s[0], callback_handle);
+    callback((sz_cptr_t)text_second, window_length, hash_mix_vec.u64s[1], callback_handle);
+    callback((sz_cptr_t)text_third, window_length, hash_mix_vec.u64s[2], callback_handle);
+    callback((sz_cptr_t)text_fourth, window_length, hash_mix_vec.u64s[3], callback_handle);
+
+    // Now repeat that operation for the remaining characters, discarding older characters.
+    sz_size_t cycle = 1;
+    sz_size_t const step_mask = step - 1;
+    for (; text_fourth != text_end; ++text_first, ++text_second, ++text_third, ++text_fourth, ++cycle) {
+        // 0. Load again the four characters we are dropping, shift them, and subtract.
+        chars_low_vec.ymm = _mm256_set_epi64x(text_fourth[-window_length], text_third[-window_length],
+                                              text_second[-window_length], text_first[-window_length]);
+        chars_high_vec.ymm = _mm256_add_epi8(chars_low_vec.ymm, shift_high_vec.ymm);
+        hash_low_vec.ymm =
+            _mm256_sub_epi64(hash_low_vec.ymm, _mm256_mul_epu64(chars_low_vec.ymm, prime_power_low_vec.ymm));
+        hash_high_vec.ymm =
+            _mm256_sub_epi64(hash_high_vec.ymm, _mm256_mul_epu64(chars_high_vec.ymm, prime_power_high_vec.ymm));
+
+        // 1. Multiply the hashes by the base.
+        hash_low_vec.ymm = _mm256_mul_epu64(hash_low_vec.ymm, base_low_vec.ymm);
+        hash_high_vec.ymm = _mm256_mul_epu64(hash_high_vec.ymm, base_high_vec.ymm);
+
+        // 2. Load the four characters from `text_first`, `text_first + max_hashes_per_thread`,
+        //   `text_first + max_hashes_per_thread * 2`, `text_first + max_hashes_per_thread * 3`.
+        chars_low_vec.ymm = _mm256_set_epi64x(text_fourth[0], text_third[0], text_second[0], text_first[0]);
+        chars_high_vec.ymm = _mm256_add_epi8(chars_low_vec.ymm, shift_high_vec.ymm);
+
+        // 3. Add the incoming characters.
+        hash_low_vec.ymm = _mm256_add_epi64(hash_low_vec.ymm, chars_low_vec.ymm);
+        hash_high_vec.ymm = _mm256_add_epi64(hash_high_vec.ymm, chars_high_vec.ymm);
+
+        // 4. Compute the modulo. Assuming there are only 59 values between our prime
+        //    and the 2^64 value, we can simply compute the modulo by conditionally subtracting the prime.
+        hash_low_vec.ymm = _mm256_blendv_epi8(hash_low_vec.ymm, _mm256_sub_epi64(hash_low_vec.ymm, prime_vec.ymm),
+                                              _mm256_cmpgt_epi64(hash_low_vec.ymm, prime_vec.ymm));
+        hash_high_vec.ymm = _mm256_blendv_epi8(hash_high_vec.ymm, _mm256_sub_epi64(hash_high_vec.ymm, prime_vec.ymm),
+                                               _mm256_cmpgt_epi64(hash_high_vec.ymm, prime_vec.ymm));
+
+        // 5. Compute the hash mix, that will be used to index into the fingerprint.
+        //    This includes a serial step at the end.
+        hash_low_vec.ymm = _mm256_mul_epu64(hash_low_vec.ymm, golden_ratio_vec.ymm);
+        hash_high_vec.ymm = _mm256_mul_epu64(hash_high_vec.ymm, golden_ratio_vec.ymm);
+        hash_mix_vec.ymm = _mm256_xor_si256(hash_low_vec.ymm, hash_high_vec.ymm);
+        if ((cycle & step_mask) == 0) {
+            callback((sz_cptr_t)text_first, window_length, hash_mix_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)text_second, window_length, hash_mix_vec.u64s[1], callback_handle);
+            callback((sz_cptr_t)text_third, window_length, hash_mix_vec.u64s[2], callback_handle);
+            callback((sz_cptr_t)text_fourth, window_length, hash_mix_vec.u64s[3], callback_handle);
+        }
+    }
+}
+
+#pragma clang attribute pop
+#pragma GCC pop_options
+#endif
+#pragma endregion
+
+/*
+ *  @brief  AVX-512 implementation of the string search algorithms.
+ *
+ *  Different subsets of AVX-512 were introduced in different years:
+ *  * 2017 SkyLake: F, CD, ER, PF, VL, DQ, BW
+ *  * 2018 CannonLake: IFMA, VBMI
+ *  * 2019 IceLake: VPOPCNTDQ, VNNI, VBMI2, BITALG, GFNI, VPCLMULQDQ, VAES
+ *  * 2020 TigerLake: VP2INTERSECT
+ */
+#pragma region AVX-512 Implementation
+
+#if SZ_USE_X86_AVX512
+#pragma GCC push_options
+#pragma GCC target("avx", "avx512f", "avx512vl", "avx512bw", "bmi", "bmi2")
+#pragma clang attribute push(__attribute__((target("avx,avx512f,avx512vl,avx512bw,bmi,bmi2"))), apply_to = function)
+#include <immintrin.h>
+
+/**
+ *  @brief  Helper structure to simplify work with 512-bit registers.
+ */
+typedef union sz_u512_vec_t {
+    __m512i zmm;
+    __m256i ymms[2];
+    __m128i xmms[4];
+    sz_u64_t u64s[8];
+    sz_u32_t u32s[16];
+    sz_u16_t u16s[32];
+    sz_u8_t u8s[64];
+    sz_i64_t i64s[8];
+    sz_i32_t i32s[16];
+} sz_u512_vec_t;
+
+SZ_INTERNAL __mmask64 _sz_u64_clamp_mask_until(sz_size_t n) {
+    // The simplest approach to compute this if we know that `n` is blow or equal 64:
+    //      return (1ull << n) - 1;
+    // A slightly more complex approach, if we don't know that `n` is under 64:
+    return _bzhi_u64(0xFFFFFFFFFFFFFFFF, n < 64 ? n : 64);
+}
+
+SZ_INTERNAL __mmask32 _sz_u32_clamp_mask_until(sz_size_t n) {
+    // The simplest approach to compute this if we know that `n` is blow or equal 32:
+    //      return (1ull << n) - 1;
+    // A slightly more complex approach, if we don't know that `n` is under 32:
+    return _bzhi_u32(0xFFFFFFFF, n < 32 ? n : 32);
+}
+
+SZ_INTERNAL __mmask16 _sz_u16_clamp_mask_until(sz_size_t n) {
+    // The simplest approach to compute this if we know that `n` is blow or equal 16:
+    //      return (1ull << n) - 1;
+    // A slightly more complex approach, if we don't know that `n` is under 16:
+    return _bzhi_u32(0xFFFFFFFF, n < 16 ? n : 16);
+}
+
+SZ_INTERNAL __mmask64 _sz_u64_mask_until(sz_size_t n) {
+    // The simplest approach to compute this if we know that `n` is blow or equal 64:
+    //      return (1ull << n) - 1;
+    // A slightly more complex approach, if we don't know that `n` is under 64:
+    return _bzhi_u64(0xFFFFFFFFFFFFFFFF, n);
+}
+
+SZ_PUBLIC sz_ordering_t sz_order_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length) {
+    sz_ordering_t ordering_lookup[2] = {sz_greater_k, sz_less_k};
+    sz_u512_vec_t a_vec, b_vec;
+    __mmask64 a_mask, b_mask, mask_not_equal;
+
+    // The rare case, when both string are very long.
+    while ((a_length >= 64) & (b_length >= 64)) {
+        a_vec.zmm = _mm512_loadu_epi8(a);
+        b_vec.zmm = _mm512_loadu_epi8(b);
+        mask_not_equal = _mm512_cmpneq_epi8_mask(a_vec.zmm, b_vec.zmm);
+        if (mask_not_equal != 0) {
+            int first_diff = _tzcnt_u64(mask_not_equal);
+            char a_char = a[first_diff];
+            char b_char = b[first_diff];
+            return ordering_lookup[a_char < b_char];
+        }
+        a += 64, b += 64, a_length -= 64, b_length -= 64;
+    }
+
+    // In most common scenarios at least one of the strings is under 64 bytes.
+    if (a_length | b_length) {
+        a_mask = _sz_u64_clamp_mask_until(a_length);
+        b_mask = _sz_u64_clamp_mask_until(b_length);
+        a_vec.zmm = _mm512_maskz_loadu_epi8(a_mask, a);
+        b_vec.zmm = _mm512_maskz_loadu_epi8(b_mask, b);
+        // The AVX-512 `_mm512_mask_cmpneq_epi8_mask` intrinsics are generally handy in such environments.
+        // They, however, have latency 3 on most modern CPUs. Using AVX2: `_mm256_cmpeq_epi8` would have
+        // been cheaper, if we didn't have to apply `_mm256_movemask_epi8` afterwards.
+        mask_not_equal = _mm512_cmpneq_epi8_mask(a_vec.zmm, b_vec.zmm);
+        if (mask_not_equal != 0) {
+            int first_diff = _tzcnt_u64(mask_not_equal);
+            char a_char = a[first_diff];
+            char b_char = b[first_diff];
+            return ordering_lookup[a_char < b_char];
+        }
+        else
+            // From logic perspective, the hardest cases are "abc\0" and "abc".
+            // The result must be `sz_greater_k`, as the latter is shorter.
+            return a_length != b_length ? ordering_lookup[a_length < b_length] : sz_equal_k;
+    }
+    else
+        return sz_equal_k;
+}
+
+SZ_PUBLIC sz_bool_t sz_equal_avx512(sz_cptr_t a, sz_cptr_t b, sz_size_t length) {
+    __mmask64 mask;
+    sz_u512_vec_t a_vec, b_vec;
+
+    while (length >= 64) {
+        a_vec.zmm = _mm512_loadu_epi8(a);
+        b_vec.zmm = _mm512_loadu_epi8(b);
+        mask = _mm512_cmpneq_epi8_mask(a_vec.zmm, b_vec.zmm);
+        if (mask != 0) return sz_false_k;
+        a += 64, b += 64, length -= 64;
+    }
+
+    if (length) {
+        mask = _sz_u64_mask_until(length);
+        a_vec.zmm = _mm512_maskz_loadu_epi8(mask, a);
+        b_vec.zmm = _mm512_maskz_loadu_epi8(mask, b);
+        // Reuse the same `mask` variable to find the bit that doesn't match
+        mask = _mm512_mask_cmpneq_epi8_mask(mask, a_vec.zmm, b_vec.zmm);
+        return (sz_bool_t)(mask == 0);
+    }
+    else
+        return sz_true_k;
+}
+
+SZ_PUBLIC void sz_fill_avx512(sz_ptr_t target, sz_size_t length, sz_u8_t value) {
+    for (; length >= 64; target += 64, length -= 64) _mm512_storeu_epi8(target, _mm512_set1_epi8(value));
+    // At this point the length is guaranteed to be under 64.
+    _mm512_mask_storeu_epi8(target, _sz_u64_mask_until(length), _mm512_set1_epi8(value));
+}
+
+SZ_PUBLIC void sz_copy_avx512(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    for (; length >= 64; target += 64, source += 64, length -= 64)
+        _mm512_storeu_epi8(target, _mm512_loadu_epi8(source));
+    // At this point the length is guaranteed to be under 64.
+    __mmask64 mask = _sz_u64_mask_until(length);
+    _mm512_mask_storeu_epi8(target, mask, _mm512_maskz_loadu_epi8(mask, source));
+}
+
+SZ_PUBLIC void sz_move_avx512(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+    if (target < source || target >= source + length) {
+        for (; length >= 64; target += 64, source += 64, length -= 64)
+            _mm512_storeu_epi8(target, _mm512_loadu_epi8(source));
+        // At this point the length is guaranteed to be under 64.
+        __mmask64 mask = _sz_u64_mask_until(length);
+        _mm512_mask_storeu_epi8(target, mask, _mm512_maskz_loadu_epi8(mask, source));
+    }
+    else {
+        // Jump to the end and walk backwards.
+        for (target += length, source += length; length >= 64; length -= 64)
+            _mm512_storeu_epi8(target -= 64, _mm512_loadu_epi8(source -= 64));
+        // At this point the length is guaranteed to be under 64.
+        __mmask64 mask = _sz_u64_mask_until(length);
+        _mm512_mask_storeu_epi8(target - length, mask, _mm512_maskz_loadu_epi8(mask, source - length));
+    }
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_byte_avx512(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+    __mmask64 mask;
+    sz_u512_vec_t h_vec, n_vec;
+    n_vec.zmm = _mm512_set1_epi8(n[0]);
+
+    while (h_length >= 64) {
+        h_vec.zmm = _mm512_loadu_epi8(h);
+        mask = _mm512_cmpeq_epi8_mask(h_vec.zmm, n_vec.zmm);
+        if (mask) return h + sz_u64_ctz(mask);
+        h += 64, h_length -= 64;
+    }
+
+    if (h_length) {
+        mask = _sz_u64_mask_until(h_length);
+        h_vec.zmm = _mm512_maskz_loadu_epi8(mask, h);
+        // Reuse the same `mask` variable to find the bit that doesn't match
+        mask = _mm512_mask_cmpeq_epu8_mask(mask, h_vec.zmm, n_vec.zmm);
+        if (mask) return h + sz_u64_ctz(mask);
+    }
+
+    return SZ_NULL;
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_avx512(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+    if (n_length == 1) return sz_find_byte_avx512(h, h_length, n);
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into ZMM registers.
+    __mmask64 matches;
+    __mmask64 mask;
+    sz_u512_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec;
+    n_first_vec.zmm = _mm512_set1_epi8(n[offset_first]);
+    n_mid_vec.zmm = _mm512_set1_epi8(n[offset_mid]);
+    n_last_vec.zmm = _mm512_set1_epi8(n[offset_last]);
+
+    // Scan through the string.
+    for (; h_length >= n_length + 64; h += 64, h_length -= 64) {
+        h_first_vec.zmm = _mm512_loadu_epi8(h + offset_first);
+        h_mid_vec.zmm = _mm512_loadu_epi8(h + offset_mid);
+        h_last_vec.zmm = _mm512_loadu_epi8(h + offset_last);
+        matches = _kand_mask64(_kand_mask64( // Intersect the masks
+                                   _mm512_cmpeq_epi8_mask(h_first_vec.zmm, n_first_vec.zmm),
+                                   _mm512_cmpeq_epi8_mask(h_mid_vec.zmm, n_mid_vec.zmm)),
+                               _mm512_cmpeq_epi8_mask(h_last_vec.zmm, n_last_vec.zmm));
+        while (matches) {
+            int potential_offset = sz_u64_ctz(matches);
+            if (n_length <= 3 || sz_equal_avx512(h + potential_offset, n, n_length)) return h + potential_offset;
+            matches &= matches - 1;
+        }
+
+        // TODO: If the last character contains a bad byte, we can reposition the start of the next iteration.
+        // This will be very helpful for very long needles.
+    }
+
+    // The "tail" of the function uses masked loads to process the remaining bytes.
+    {
+        mask = _sz_u64_mask_until(h_length - n_length + 1);
+        h_first_vec.zmm = _mm512_maskz_loadu_epi8(mask, h + offset_first);
+        h_mid_vec.zmm = _mm512_maskz_loadu_epi8(mask, h + offset_mid);
+        h_last_vec.zmm = _mm512_maskz_loadu_epi8(mask, h + offset_last);
+        matches = _kand_mask64(_kand_mask64( // Intersect the masks
+                                   _mm512_cmpeq_epi8_mask(h_first_vec.zmm, n_first_vec.zmm),
+                                   _mm512_cmpeq_epi8_mask(h_mid_vec.zmm, n_mid_vec.zmm)),
+                               _mm512_cmpeq_epi8_mask(h_last_vec.zmm, n_last_vec.zmm));
+        while (matches) {
+            int potential_offset = sz_u64_ctz(matches);
+            if (n_length <= 3 || sz_equal_avx512(h + potential_offset, n, n_length)) return h + potential_offset;
+            matches &= matches - 1;
+        }
+    }
+    return SZ_NULL;
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_avx512(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+    __mmask64 mask;
+    sz_u512_vec_t h_vec, n_vec;
+    n_vec.zmm = _mm512_set1_epi8(n[0]);
+
+    while (h_length >= 64) {
+        h_vec.zmm = _mm512_loadu_epi8(h + h_length - 64);
+        mask = _mm512_cmpeq_epi8_mask(h_vec.zmm, n_vec.zmm);
+        if (mask) return h + h_length - 1 - sz_u64_clz(mask);
+        h_length -= 64;
+    }
+
+    if (h_length) {
+        mask = _sz_u64_mask_until(h_length);
+        h_vec.zmm = _mm512_maskz_loadu_epi8(mask, h);
+        // Reuse the same `mask` variable to find the bit that doesn't match
+        mask = _mm512_mask_cmpeq_epu8_mask(mask, h_vec.zmm, n_vec.zmm);
+        if (mask) return h + 64 - sz_u64_clz(mask) - 1;
+    }
+
+    return SZ_NULL;
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_avx512(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+    if (n_length == 1) return sz_rfind_byte_avx512(h, h_length, n);
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into ZMM registers.
+    __mmask64 mask;
+    __mmask64 matches;
+    sz_u512_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec;
+    n_first_vec.zmm = _mm512_set1_epi8(n[offset_first]);
+    n_mid_vec.zmm = _mm512_set1_epi8(n[offset_mid]);
+    n_last_vec.zmm = _mm512_set1_epi8(n[offset_last]);
+
+    // Scan through the string.
+    sz_cptr_t h_reversed;
+    for (; h_length >= n_length + 64; h_length -= 64) {
+        h_reversed = h + h_length - n_length - 64 + 1;
+        h_first_vec.zmm = _mm512_loadu_epi8(h_reversed + offset_first);
+        h_mid_vec.zmm = _mm512_loadu_epi8(h_reversed + offset_mid);
+        h_last_vec.zmm = _mm512_loadu_epi8(h_reversed + offset_last);
+        matches = _kand_mask64(_kand_mask64( // Intersect the masks
+                                   _mm512_cmpeq_epi8_mask(h_first_vec.zmm, n_first_vec.zmm),
+                                   _mm512_cmpeq_epi8_mask(h_mid_vec.zmm, n_mid_vec.zmm)),
+                               _mm512_cmpeq_epi8_mask(h_last_vec.zmm, n_last_vec.zmm));
+        while (matches) {
+            int potential_offset = sz_u64_clz(matches);
+            if (n_length <= 3 || sz_equal_avx512(h + h_length - n_length - potential_offset, n, n_length))
+                return h + h_length - n_length - potential_offset;
+            sz_assert((matches & ((sz_u64_t)1 << (63 - potential_offset))) != 0 &&
+                      "The bit must be set before we squash it");
+            matches &= ~((sz_u64_t)1 << (63 - potential_offset));
+        }
+    }
+
+    // The "tail" of the function uses masked loads to process the remaining bytes.
+    {
+        mask = _sz_u64_mask_until(h_length - n_length + 1);
+        h_first_vec.zmm = _mm512_maskz_loadu_epi8(mask, h + offset_first);
+        h_mid_vec.zmm = _mm512_maskz_loadu_epi8(mask, h + offset_mid);
+        h_last_vec.zmm = _mm512_maskz_loadu_epi8(mask, h + offset_last);
+        matches = _kand_mask64(_kand_mask64( // Intersect the masks
+                                   _mm512_cmpeq_epi8_mask(h_first_vec.zmm, n_first_vec.zmm),
+                                   _mm512_cmpeq_epi8_mask(h_mid_vec.zmm, n_mid_vec.zmm)),
+                               _mm512_cmpeq_epi8_mask(h_last_vec.zmm, n_last_vec.zmm));
+        while (matches) {
+            int potential_offset = sz_u64_clz(matches);
+            if (n_length <= 3 || sz_equal_avx512(h + 64 - potential_offset - 1, n, n_length))
+                return h + 64 - potential_offset - 1;
+            sz_assert((matches & ((sz_u64_t)1 << (63 - potential_offset))) != 0 &&
+                      "The bit must be set before we squash it");
+            matches &= ~((sz_u64_t)1 << (63 - potential_offset));
+        }
+    }
+
+    return SZ_NULL;
+}
+
+SZ_INTERNAL sz_size_t _sz_edit_distance_skewed_diagonals_upto65k_avx512( //
+    sz_cptr_t shorter, sz_size_t shorter_length,                         //
+    sz_cptr_t longer, sz_size_t longer_length,                           //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+
+    // Simplify usage in higher-level libraries, where wrapping custom allocators may be troublesome.
+    sz_memory_allocator_t global_alloc;
+    if (!alloc) {
+        sz_memory_allocator_init_default(&global_alloc);
+        alloc = &global_alloc;
+    }
+
+    // TODO: Generalize!
+    sz_size_t max_length = 256u * 256u;
+    sz_assert(!bound && "For bounded search the method should only evaluate one band of the matrix.");
+    sz_assert(shorter_length == longer_length && "The method hasn't been generalized to different length inputs yet.");
+    sz_assert(shorter_length < max_length && "The length must fit into 16-bit integer. Otherwise use serial variant.");
+    sz_unused(longer_length && bound && max_length);
+
+    // We are going to store 3 diagonals of the matrix.
+    // The length of the longest (main) diagonal would be `n = (shorter_length + 1)`.
+    sz_size_t n = shorter_length + 1;
+    // Unlike the serial version, we also want to avoid reverse-order iteration over teh shorter string.
+    // So let's allocate a bit more memory and reverse-export our shorter string into that buffer.
+    sz_size_t buffer_length = sizeof(sz_u16_t) * n * 3 + shorter_length;
+    sz_u16_t *distances = (sz_u16_t *)alloc->allocate(buffer_length, alloc->handle);
+    if (!distances) return SZ_SIZE_MAX;
+
+    sz_u16_t *previous_distances = distances;
+    sz_u16_t *current_distances = previous_distances + n;
+    sz_u16_t *next_distances = current_distances + n;
+    sz_ptr_t shorter_reversed = (sz_ptr_t)(next_distances + n);
+
+    // Export the reversed string into the buffer.
+    for (sz_size_t i = 0; i != shorter_length; ++i) shorter_reversed[i] = shorter[shorter_length - 1 - i];
+
+    // Initialize the first two diagonals:
+    previous_distances[0] = 0;
+    current_distances[0] = current_distances[1] = 1;
+
+    // Using ZMM registers, we can process 32x 16-bit values at once,
+    // storing 16 bytes of each string in YMM registers.
+    sz_u512_vec_t insertions_vec, deletions_vec, substitutions_vec, next_vec;
+    sz_u512_vec_t ones_u16_vec;
+    ones_u16_vec.zmm = _mm512_set1_epi16(1);
+    // This is a mixed-precision implementation, using 8-bit representations for part of the operations.
+    // Even there, in case `SZ_USE_X86_AVX2=0`, let's use the `sz_u512_vec_t` type, addressing the first YMM halfs.
+    sz_u512_vec_t shorter_vec, longer_vec;
+    sz_u512_vec_t ones_u8_vec;
+    ones_u8_vec.ymms[0] = _mm256_set1_epi8(1);
+
+    // Progress through the upper triangle of the Levenshtein matrix.
+    sz_size_t next_skew_diagonal_index = 2;
+    for (; next_skew_diagonal_index != n; ++next_skew_diagonal_index) {
+        sz_size_t const next_skew_diagonal_length = next_skew_diagonal_index + 1;
+        for (sz_size_t i = 0; i + 2 < next_skew_diagonal_length;) {
+            sz_size_t remaining_length = next_skew_diagonal_length - i - 2;
+            sz_size_t register_length = remaining_length < 32 ? remaining_length : 32;
+            sz_u32_t remaining_length_mask = _bzhi_u32(0xFFFFFFFFu, register_length);
+            longer_vec.ymms[0] = _mm256_maskz_loadu_epi8(remaining_length_mask, longer + i);
+            // Our original code addressed the shorter string `[next_skew_diagonal_index - i - 2]` for growing `i`.
+            // If the `shorter` string was reversed, the `[next_skew_diagonal_index - i - 2]` would
+            // be equal to `[shorter_length - 1 - next_skew_diagonal_index + i + 2]`.
+            // Which simplified would be equal to `[shorter_length - next_skew_diagonal_index + i + 1]`.
+            shorter_vec.ymms[0] = _mm256_maskz_loadu_epi8(
+                remaining_length_mask, shorter_reversed + shorter_length - next_skew_diagonal_index + i + 1);
+            // For substitutions, perform the equality comparison using AVX2 instead of AVX-512
+            // to get the result as a vector, instead of a bitmask. Adding 1 to every scalar we can overflow
+            // transforming from {0xFF, 0} values to {0, 1} values - exactly what we need. Then - upcast to 16-bit.
+            substitutions_vec.zmm = _mm512_cvtepi8_epi16( //
+                _mm256_add_epi8(_mm256_cmpeq_epi8(longer_vec.ymms[0], shorter_vec.ymms[0]), ones_u8_vec.ymms[0]));
+            substitutions_vec.zmm = _mm512_add_epi16( //
+                substitutions_vec.zmm, _mm512_maskz_loadu_epi16(remaining_length_mask, previous_distances + i));
+            // For insertions and deletions, on modern hardware, it's faster to issue two separate loads,
+            // than rotate the bytes in the ZMM register.
+            insertions_vec.zmm = _mm512_maskz_loadu_epi16(remaining_length_mask, current_distances + i);
+            deletions_vec.zmm = _mm512_maskz_loadu_epi16(remaining_length_mask, current_distances + i + 1);
+            // First get the minimum of insertions and deletions.
+            next_vec.zmm = _mm512_add_epi16(_mm512_min_epu16(insertions_vec.zmm, deletions_vec.zmm), ones_u16_vec.zmm);
+            next_vec.zmm = _mm512_min_epu16(next_vec.zmm, substitutions_vec.zmm);
+            _mm512_mask_storeu_epi16(next_distances + i + 1, remaining_length_mask, next_vec.zmm);
+            i += register_length;
+        }
+        // Don't forget to populate the first row and the fiest column of the Levenshtein matrix.
+        next_distances[0] = next_distances[next_skew_diagonal_length - 1] = next_skew_diagonal_index;
+        // Perform a circular rotarion of those buffers, to reuse the memory.
+        sz_u16_t *temporary = previous_distances;
+        previous_distances = current_distances;
+        current_distances = next_distances;
+        next_distances = temporary;
+    }
+
+    // By now we've scanned through the upper triangle of the matrix, where each subsequent iteration results in a
+    // larger diagonal. From now onwards, we will be shrinking. Instead of adding value equal to the skewed diagonal
+    // index on either side, we will be cropping those values out.
+    sz_size_t total_diagonals = n + n - 1;
+    for (; next_skew_diagonal_index != total_diagonals; ++next_skew_diagonal_index) {
+        sz_size_t const next_skew_diagonal_length = total_diagonals - next_skew_diagonal_index;
+        for (sz_size_t i = 0; i != next_skew_diagonal_length;) {
+            sz_size_t remaining_length = next_skew_diagonal_length - i;
+            sz_size_t register_length = remaining_length < 32 ? remaining_length : 32;
+            sz_u32_t remaining_length_mask = _bzhi_u32(0xFFFFFFFFu, register_length);
+            longer_vec.ymms[0] =
+                _mm256_maskz_loadu_epi8(remaining_length_mask, longer + next_skew_diagonal_index - n + i);
+            // Our original code addressed the shorter string `[shorter_length - 1 - i]` for growing `i`.
+            // If the `shorter` string was reversed, the `[shorter_length - 1 - i]` would
+            // be equal to `[shorter_length - 1 - shorter_length + 1 + i]`.
+            // Which simplified would be equal to just `[i]`. Beautiful!
+            shorter_vec.ymms[0] = _mm256_maskz_loadu_epi8(remaining_length_mask, shorter_reversed + i);
+            // For substitutions, perform the equality comparison using AVX2 instead of AVX-512
+            // to get the result as a vector, instead of a bitmask. The compare it against the accumulated
+            // substitution costs.
+            substitutions_vec.zmm = _mm512_cvtepi8_epi16( //
+                _mm256_add_epi8(_mm256_cmpeq_epi8(longer_vec.ymms[0], shorter_vec.ymms[0]), ones_u8_vec.ymms[0]));
+            substitutions_vec.zmm = _mm512_add_epi16( //
+                substitutions_vec.zmm, _mm512_maskz_loadu_epi16(remaining_length_mask, previous_distances + i));
+            // For insertions and deletions, on modern hardware, it's faster to issue two separate loads,
+            // than rotate the bytes in the ZMM register.
+            insertions_vec.zmm = _mm512_maskz_loadu_epi16(remaining_length_mask, current_distances + i);
+            deletions_vec.zmm = _mm512_maskz_loadu_epi16(remaining_length_mask, current_distances + i + 1);
+            // First get the minimum of insertions and deletions.
+            next_vec.zmm = _mm512_add_epi16(_mm512_min_epu16(insertions_vec.zmm, deletions_vec.zmm), ones_u16_vec.zmm);
+            next_vec.zmm = _mm512_min_epu16(next_vec.zmm, substitutions_vec.zmm);
+            _mm512_mask_storeu_epi16(next_distances + i, remaining_length_mask, next_vec.zmm);
+            i += register_length;
+        }
+
+        // Perform a circular rotarion of those buffers, to reuse the memory, this time, with a shift,
+        // dropping the first element in the current array.
+        sz_u16_t *temporary = previous_distances;
+        previous_distances = current_distances + 1;
+        current_distances = next_distances;
+        next_distances = temporary;
+    }
+
+    // Cache scalar before `free` call.
+    sz_size_t result = current_distances[0];
+    alloc->free(distances, buffer_length, alloc->handle);
+    return result;
+}
+
+SZ_INTERNAL sz_size_t sz_edit_distance_avx512(   //
+    sz_cptr_t shorter, sz_size_t shorter_length, //
+    sz_cptr_t longer, sz_size_t longer_length,   //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+
+    if (shorter_length == longer_length && !bound && shorter_length && shorter_length < 256u * 256u)
+        return _sz_edit_distance_skewed_diagonals_upto65k_avx512(shorter, shorter_length, longer, longer_length, bound,
+                                                                 alloc);
+    else
+        return sz_edit_distance_serial(shorter, shorter_length, longer, longer_length, bound, alloc);
+}
+
+#pragma clang attribute pop
+#pragma GCC pop_options
+
+#pragma GCC push_options
+#pragma GCC target("avx", "avx512f", "avx512vl", "avx512bw", "avx512dq", "bmi", "bmi2")
+#pragma clang attribute push(__attribute__((target("avx,avx512f,avx512vl,avx512bw,avx512dq,bmi,bmi2"))), \
+                             apply_to = function)
+
+SZ_PUBLIC void sz_hashes_avx512(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
+                                sz_hash_callback_t callback, void *callback_handle) {
+
+    if (length < window_length || !window_length) return;
+    if (length < 4 * window_length) {
+        sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
+        return;
+    }
+
+    // Using AVX2, we can perform 4 long integer multiplications and additions within one register.
+    // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
+    sz_size_t const max_hashes = length - window_length + 1;
+    sz_size_t const min_hashes_per_thread = max_hashes / 4; // At most one sequence can overlap between 2 threads.
+    sz_u8_t const *text_first = (sz_u8_t const *)start;
+    sz_u8_t const *text_second = text_first + min_hashes_per_thread;
+    sz_u8_t const *text_third = text_first + min_hashes_per_thread * 2;
+    sz_u8_t const *text_fourth = text_first + min_hashes_per_thread * 3;
+    sz_u8_t const *text_end = text_first + length;
+
+    // Broadcast the global constants into the registers.
+    // Both high and low hashes will work with the same prime and golden ratio.
+    sz_u512_vec_t prime_vec, golden_ratio_vec;
+    prime_vec.zmm = _mm512_set1_epi64(SZ_U64_MAX_PRIME);
+    golden_ratio_vec.zmm = _mm512_set1_epi64(11400714819323198485ull);
+
+    // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
+    sz_u64_t prime_power_low = 1, prime_power_high = 1;
+    for (sz_size_t i = 0; i + 1 < window_length; ++i)
+        prime_power_low = (prime_power_low * 31ull) % SZ_U64_MAX_PRIME,
+        prime_power_high = (prime_power_high * 257ull) % SZ_U64_MAX_PRIME;
+
+    // We will be evaluating 4 offsets at a time with 2 different hash functions.
+    // We can fit all those 8 state variables in each of the following ZMM registers.
+    sz_u512_vec_t base_vec, prime_power_vec, shift_vec;
+    base_vec.zmm = _mm512_set_epi64(31ull, 31ull, 31ull, 31ull, 257ull, 257ull, 257ull, 257ull);
+    shift_vec.zmm = _mm512_set_epi64(0ull, 0ull, 0ull, 0ull, 77ull, 77ull, 77ull, 77ull);
+    prime_power_vec.zmm = _mm512_set_epi64(prime_power_low, prime_power_low, prime_power_low, prime_power_low,
+                                           prime_power_high, prime_power_high, prime_power_high, prime_power_high);
+
+    // Compute the initial hash values for every one of the four windows.
+    sz_u512_vec_t hash_vec, chars_vec;
+    hash_vec.zmm = _mm512_setzero_si512();
+    for (sz_u8_t const *prefix_end = text_first + window_length; text_first < prefix_end;
+         ++text_first, ++text_second, ++text_third, ++text_fourth) {
+
+        // 1. Multiply the hashes by the base.
+        hash_vec.zmm = _mm512_mullo_epi64(hash_vec.zmm, base_vec.zmm);
+
+        // 2. Load the four characters from `text_first`, `text_first + max_hashes_per_thread`,
+        //   `text_first + max_hashes_per_thread * 2`, `text_first + max_hashes_per_thread * 3`...
+        chars_vec.zmm = _mm512_set_epi64(text_fourth[0], text_third[0], text_second[0], text_first[0], //
+                                         text_fourth[0], text_third[0], text_second[0], text_first[0]);
+        chars_vec.zmm = _mm512_add_epi8(chars_vec.zmm, shift_vec.zmm);
+
+        // 3. Add the incoming characters.
+        hash_vec.zmm = _mm512_add_epi64(hash_vec.zmm, chars_vec.zmm);
+
+        // 4. Compute the modulo. Assuming there are only 59 values between our prime
+        //    and the 2^64 value, we can simply compute the modulo by conditionally subtracting the prime.
+        hash_vec.zmm = _mm512_mask_blend_epi8(_mm512_cmpgt_epi64_mask(hash_vec.zmm, prime_vec.zmm), hash_vec.zmm,
+                                              _mm512_sub_epi64(hash_vec.zmm, prime_vec.zmm));
+    }
+
+    // 5. Compute the hash mix, that will be used to index into the fingerprint.
+    //    This includes a serial step at the end.
+    sz_u512_vec_t hash_mix_vec;
+    hash_mix_vec.zmm = _mm512_mullo_epi64(hash_vec.zmm, golden_ratio_vec.zmm);
+    hash_mix_vec.ymms[0] = _mm256_xor_si256(_mm512_extracti64x4_epi64(hash_mix_vec.zmm, 1), //
+                                            _mm512_extracti64x4_epi64(hash_mix_vec.zmm, 0));
+
+    callback((sz_cptr_t)text_first, window_length, hash_mix_vec.u64s[0], callback_handle);
+    callback((sz_cptr_t)text_second, window_length, hash_mix_vec.u64s[1], callback_handle);
+    callback((sz_cptr_t)text_third, window_length, hash_mix_vec.u64s[2], callback_handle);
+    callback((sz_cptr_t)text_fourth, window_length, hash_mix_vec.u64s[3], callback_handle);
+
+    // Now repeat that operation for the remaining characters, discarding older characters.
+    sz_size_t cycle = 1;
+    sz_size_t step_mask = step - 1;
+    for (; text_fourth != text_end; ++text_first, ++text_second, ++text_third, ++text_fourth, ++cycle) {
+        // 0. Load again the four characters we are dropping, shift them, and subtract.
+        chars_vec.zmm = _mm512_set_epi64(text_fourth[-window_length], text_third[-window_length],
+                                         text_second[-window_length], text_first[-window_length], //
+                                         text_fourth[-window_length], text_third[-window_length],
+                                         text_second[-window_length], text_first[-window_length]);
+        chars_vec.zmm = _mm512_add_epi8(chars_vec.zmm, shift_vec.zmm);
+        hash_vec.zmm = _mm512_sub_epi64(hash_vec.zmm, _mm512_mullo_epi64(chars_vec.zmm, prime_power_vec.zmm));
+
+        // 1. Multiply the hashes by the base.
+        hash_vec.zmm = _mm512_mullo_epi64(hash_vec.zmm, base_vec.zmm);
+
+        // 2. Load the four characters from `text_first`, `text_first + max_hashes_per_thread`,
+        //   `text_first + max_hashes_per_thread * 2`, `text_first + max_hashes_per_thread * 3`.
+        chars_vec.zmm = _mm512_set_epi64(text_fourth[0], text_third[0], text_second[0], text_first[0], //
+                                         text_fourth[0], text_third[0], text_second[0], text_first[0]);
+        chars_vec.zmm = _mm512_add_epi8(chars_vec.zmm, shift_vec.zmm);
+
+        // ... and prefetch the next four characters into Level 2 or higher.
+        _mm_prefetch(text_fourth + 1, _MM_HINT_T1);
+        _mm_prefetch(text_third + 1, _MM_HINT_T1);
+        _mm_prefetch(text_second + 1, _MM_HINT_T1);
+        _mm_prefetch(text_first + 1, _MM_HINT_T1);
+
+        // 3. Add the incoming characters.
+        hash_vec.zmm = _mm512_add_epi64(hash_vec.zmm, chars_vec.zmm);
+
+        // 4. Compute the modulo. Assuming there are only 59 values between our prime
+        //    and the 2^64 value, we can simply compute the modulo by conditionally subtracting the prime.
+        hash_vec.zmm = _mm512_mask_blend_epi8(_mm512_cmpgt_epi64_mask(hash_vec.zmm, prime_vec.zmm), hash_vec.zmm,
+                                              _mm512_sub_epi64(hash_vec.zmm, prime_vec.zmm));
+
+        // 5. Compute the hash mix, that will be used to index into the fingerprint.
+        //    This includes a serial step at the end.
+        hash_mix_vec.zmm = _mm512_mullo_epi64(hash_vec.zmm, golden_ratio_vec.zmm);
+        hash_mix_vec.ymms[0] = _mm256_xor_si256(_mm512_extracti64x4_epi64(hash_mix_vec.zmm, 1), //
+                                                _mm512_castsi512_si256(hash_mix_vec.zmm));
+
+        if ((cycle & step_mask) == 0) {
+            callback((sz_cptr_t)text_first, window_length, hash_mix_vec.u64s[0], callback_handle);
+            callback((sz_cptr_t)text_second, window_length, hash_mix_vec.u64s[1], callback_handle);
+            callback((sz_cptr_t)text_third, window_length, hash_mix_vec.u64s[2], callback_handle);
+            callback((sz_cptr_t)text_fourth, window_length, hash_mix_vec.u64s[3], callback_handle);
+        }
+    }
+}
+
+#pragma clang attribute pop
+#pragma GCC pop_options
+
+#pragma GCC push_options
+#pragma GCC target("avx", "avx512f", "avx512vl", "avx512bw", "avx512vbmi", "bmi", "bmi2", "gfni")
+#pragma clang attribute push(__attribute__((target("avx,avx512f,avx512vl,avx512bw,avx512vbmi,bmi,bmi2,gfni"))), \
+                             apply_to = function)
+
+SZ_PUBLIC sz_cptr_t sz_find_charset_avx512(sz_cptr_t text, sz_size_t length, sz_charset_t const *filter) {
+
+    sz_size_t load_length;
+    __mmask32 load_mask, matches_mask;
+    // To store the set in the register we need just 256 bits, but the `VPERMB` instruction
+    // we are going to invoke is surprisingly cheaper on ZMM registers.
+    sz_u512_vec_t text_vec, filter_vec;
+    filter_vec.ymms[0] = _mm256_loadu_epi64(&filter->_u64s[0]);
+
+    // We are going to view the `filter` at 8-bit word granularity.
+    sz_u512_vec_t filter_slice_offsets_vec;
+    sz_u512_vec_t filter_slice_vec;
+    sz_u512_vec_t offset_within_slice_vec;
+    sz_u512_vec_t mask_in_filter_slice_vec;
+    sz_u512_vec_t matches_vec;
+
+    while (length) {
+        // For every byte:
+        // 1. Find corresponding word in a set.
+        // 2. Produce a bitmask to check against that word.
+        load_length = sz_min_of_two(length, 32);
+        load_mask = _sz_u64_mask_until(load_length);
+        text_vec.ymms[0] = _mm256_maskz_loadu_epi8(load_mask, text);
+
+        // To shift right every byte by 3 bits we can use the GF2 affine transformations.
+        // https://wunkolo.github.io/post/2020/11/gf2p8affineqb-int8-shifting/
+        // After next line, all 8-bit offsets in the `filter_slice_offsets_vec` should be under 32.
+        filter_slice_offsets_vec.ymms[0] =
+            _mm256_gf2p8affine_epi64_epi8(text_vec.ymms[0], _mm256_set1_epi64x(0x0102040810204080ull << (3 * 8)), 0);
+
+        // After next line, `filter_slice_vec` will contain the right word from the set,
+        // needed to filter the presence of the byte in the set.
+        filter_slice_vec.ymms[0] = _mm256_permutexvar_epi8(filter_slice_offsets_vec.ymms[0], filter_vec.ymms[0]);
+
+        // After next line, all 8-bit offsets in the `filter_slice_offsets_vec` should be under 8.
+        offset_within_slice_vec.ymms[0] = _mm256_and_si256(text_vec.ymms[0], _mm256_set1_epi64x(0x0707070707070707ull));
+
+        // Instead of performing one more Galois Field operation, we can upcast to 16-bit integers,
+        // and perform the fift and intersection there.
+        filter_slice_vec.zmm = _mm512_cvtepi8_epi16(filter_slice_vec.ymms[0]);
+        offset_within_slice_vec.zmm = _mm512_cvtepi8_epi16(offset_within_slice_vec.ymms[0]);
+        mask_in_filter_slice_vec.zmm = _mm512_sllv_epi16(_mm512_set1_epi16(1), offset_within_slice_vec.zmm);
+        matches_vec.zmm = _mm512_and_si512(filter_slice_vec.zmm, mask_in_filter_slice_vec.zmm);
+
+        matches_mask = _mm512_mask_cmpneq_epi16_mask(load_mask, matches_vec.zmm, _mm512_setzero_si512());
+        if (matches_mask) {
+            int offset = sz_u32_ctz(matches_mask);
+            return text + offset;
+        }
+        else { text += load_length, length -= load_length; }
+    }
+
+    return SZ_NULL;
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_charset_avx512(sz_cptr_t text, sz_size_t length, sz_charset_t const *filter) {
+
+    sz_size_t load_length;
+    __mmask32 load_mask, matches_mask;
+    // To store the set in the register we need just 256 bits, but the `VPERMB` instruction
+    // we are going to invoke is surprisingly cheaper on ZMM registers.
+    sz_u512_vec_t text_vec, filter_vec;
+    filter_vec.ymms[0] = _mm256_loadu_epi64(&filter->_u64s[0]);
+
+    // We are going to view the `filter` at 8-bit word granularity.
+    sz_u512_vec_t filter_slice_offsets_vec;
+    sz_u512_vec_t filter_slice_vec;
+    sz_u512_vec_t offset_within_slice_vec;
+    sz_u512_vec_t mask_in_filter_slice_vec;
+    sz_u512_vec_t matches_vec;
+
+    while (length) {
+        // For every byte:
+        // 1. Find corresponding word in a set.
+        // 2. Produce a bitmask to check against that word.
+        load_length = sz_min_of_two(length, 32);
+        load_mask = _sz_u64_mask_until(load_length);
+        text_vec.ymms[0] = _mm256_maskz_loadu_epi8(load_mask, text + length - load_length);
+
+        // To shift right every byte by 3 bits we can use the GF2 affine transformations.
+        // https://wunkolo.github.io/post/2020/11/gf2p8affineqb-int8-shifting/
+        // After next line, all 8-bit offsets in the `filter_slice_offsets_vec` should be under 32.
+        filter_slice_offsets_vec.ymms[0] =
+            _mm256_gf2p8affine_epi64_epi8(text_vec.ymms[0], _mm256_set1_epi64x(0x0102040810204080ull << (3 * 8)), 0);
+
+        // After next line, `filter_slice_vec` will contain the right word from the set,
+        // needed to filter the presence of the byte in the set.
+        filter_slice_vec.ymms[0] = _mm256_permutexvar_epi8(filter_slice_offsets_vec.ymms[0], filter_vec.ymms[0]);
+
+        // After next line, all 8-bit offsets in the `filter_slice_offsets_vec` should be under 8.
+        offset_within_slice_vec.ymms[0] = _mm256_and_si256(text_vec.ymms[0], _mm256_set1_epi64x(0x0707070707070707ull));
+
+        // Instead of performing one more Galois Field operation, we can upcast to 16-bit integers,
+        // and perform the fift and intersection there.
+        filter_slice_vec.zmm = _mm512_cvtepi8_epi16(filter_slice_vec.ymms[0]);
+        offset_within_slice_vec.zmm = _mm512_cvtepi8_epi16(offset_within_slice_vec.ymms[0]);
+        mask_in_filter_slice_vec.zmm = _mm512_sllv_epi16(_mm512_set1_epi16(1), offset_within_slice_vec.zmm);
+        matches_vec.zmm = _mm512_and_si512(filter_slice_vec.zmm, mask_in_filter_slice_vec.zmm);
+
+        matches_mask = _mm512_mask_cmpneq_epi16_mask(load_mask, matches_vec.zmm, _mm512_setzero_si512());
+        if (matches_mask) {
+            int offset = sz_u32_clz(matches_mask);
+            return text + length - load_length + 32 - offset - 1;
+        }
+        else { length -= load_length; }
+    }
+
+    return SZ_NULL;
+}
+
+/**
+ *  Computes the Needleman Wunsch alignment score between two strings.
+ *  The method uses 32-bit integers to accumulate the running score for every cell in the matrix.
+ *  Assuming the costs of substitutions can be arbitrary signed 8-bit integers, the method is expected to be used
+ *  on strings not exceeding 2^24 length or 16.7 million characters.
+ *
+ *  Unlike the `_sz_edit_distance_skewed_diagonals_upto65k_avx512` method, this one uses signed integers to store
+ *  the accumulated score. Moreover, it's primary bottleneck is the latency of gathering the substitution costs
+ *  from the substitution matrix. If we use the diagonal order, we will be comparing a slice of the first string with
+ *  a slice of the second. If we stick to the conventional horizontal order, we will be comparing one character against
+ *  a slice, which is much easier to optimize. In that case we are sampling costs not from arbitrary parts of
+ *  a 256 x 256 matrix, but from a single row!
+ */
+SZ_INTERNAL sz_ssize_t _sz_alignment_score_wagner_fisher_upto17m_avx512( //
+    sz_cptr_t shorter, sz_size_t shorter_length,                         //
+    sz_cptr_t longer, sz_size_t longer_length,                           //
+    sz_error_cost_t const *subs, sz_error_cost_t gap, sz_memory_allocator_t *alloc) {
+
+    // If one of the strings is empty - the edit distance is equal to the length of the other one
+    if (longer_length == 0) return (sz_ssize_t)shorter_length * gap;
+    if (shorter_length == 0) return (sz_ssize_t)longer_length * gap;
+
+    // Let's make sure that we use the amount proportional to the
+    // number of elements in the shorter string, not the larger.
+    if (shorter_length > longer_length) {
+        sz_u64_swap((sz_u64_t *)&longer_length, (sz_u64_t *)&shorter_length);
+        sz_u64_swap((sz_u64_t *)&longer, (sz_u64_t *)&shorter);
+    }
+
+    // Simplify usage in higher-level libraries, where wrapping custom allocators may be troublesome.
+    sz_memory_allocator_t global_alloc;
+    if (!alloc) {
+        sz_memory_allocator_init_default(&global_alloc);
+        alloc = &global_alloc;
+    }
+
+    sz_size_t const max_length = 256ull * 256ull * 256ull;
+    sz_size_t const n = longer_length + 1;
+    sz_assert(n < max_length && "The length must fit into 24-bit integer. Otherwise use serial variant.");
+    sz_unused(longer_length && max_length);
+
+    sz_size_t buffer_length = sizeof(sz_i32_t) * n * 2;
+    sz_i32_t *distances = (sz_i32_t *)alloc->allocate(buffer_length, alloc->handle);
+    sz_i32_t *previous_distances = distances;
+    sz_i32_t *current_distances = previous_distances + n;
+
+    // Intialize the first row of the Levenshtein matrix with `iota`.
+    for (sz_size_t idx_longer = 0; idx_longer != n; ++idx_longer)
+        previous_distances[idx_longer] = (sz_ssize_t)idx_longer * gap;
+
+    /// Contains up to 16 consecutive characters from the longer string.
+    sz_u512_vec_t longer_vec;
+    sz_u512_vec_t cost_deletion_vec, cost_substitution_vec, lookup_substitution_vec, current_vec;
+    sz_u512_vec_t row_first_subs_vec, row_second_subs_vec, row_third_subs_vec, row_fourth_subs_vec;
+    sz_u512_vec_t shuffled_first_subs_vec, shuffled_second_subs_vec, shuffled_third_subs_vec, shuffled_fourth_subs_vec;
+
+    // Prepare constants and masks.
+    sz_u512_vec_t is_third_or_fourth_vec, is_second_or_fourth_vec, gap_vec;
+    {
+        char is_third_or_fourth_check, is_second_or_fourth_check;
+        *(sz_u8_t *)&is_third_or_fourth_check = 0x80, *(sz_u8_t *)&is_second_or_fourth_check = 0x40;
+        is_third_or_fourth_vec.zmm = _mm512_set1_epi8(is_third_or_fourth_check);
+        is_second_or_fourth_vec.zmm = _mm512_set1_epi8(is_second_or_fourth_check);
+        gap_vec.zmm = _mm512_set1_epi32(gap);
+    }
+
+    sz_u8_t const *shorter_unsigned = (sz_u8_t const *)shorter;
+    for (sz_size_t idx_shorter = 0; idx_shorter != shorter_length; ++idx_shorter) {
+        sz_i32_t last_in_row = current_distances[0] = (sz_ssize_t)(idx_shorter + 1) * gap;
+
+        // Load one row of the substitution matrix into four ZMM registers.
+        sz_error_cost_t const *row_subs = subs + shorter_unsigned[idx_shorter] * 256u;
+        row_first_subs_vec.zmm = _mm512_loadu_epi8(row_subs + 64 * 0);
+        row_second_subs_vec.zmm = _mm512_loadu_epi8(row_subs + 64 * 1);
+        row_third_subs_vec.zmm = _mm512_loadu_epi8(row_subs + 64 * 2);
+        row_fourth_subs_vec.zmm = _mm512_loadu_epi8(row_subs + 64 * 3);
+
+        // In the serial version we have one forward pass, that computes the deletion,
+        // insertion, and substitution costs at once.
+        //    for (sz_size_t idx_longer = 0; idx_longer < longer_length; ++idx_longer) {
+        //        sz_ssize_t cost_deletion = previous_distances[idx_longer + 1] + gap;
+        //        sz_ssize_t cost_insertion = current_distances[idx_longer] + gap;
+        //        sz_ssize_t cost_substitution = previous_distances[idx_longer] + row_subs[longer_unsigned[idx_longer]];
+        //        current_distances[idx_longer + 1] = sz_min_of_three(cost_deletion, cost_insertion, cost_substitution);
+        //    }
+        //
+        // Given the complexity of handling the data-dependency between consecutive insertion cost computations
+        // within a Levenshtein matrix, the simplest design would be to vectorize every kind of cost computation
+        // separately.
+        //      1. Compute substitution costs for up to 64 characters at once, upcasting from 8-bit integers to 32.
+        //      2. Compute the pairwise minimum with deletion costs.
+        //      3. Inclusive prefix minimum computation to combine with addition costs.
+        // Proceeding with substitutions:
+        for (sz_size_t idx_longer = 0; idx_longer < longer_length; idx_longer += 64) {
+            sz_size_t register_length = sz_min_of_two(longer_length - idx_longer, 64);
+            __mmask64 mask = _sz_u64_mask_until(register_length);
+            longer_vec.zmm = _mm512_maskz_loadu_epi8(mask, longer + idx_longer);
+
+            // Blend the `row_(first|second|third|fourth)_subs_vec` into `current_vec`, picking the right source
+            // for every character in `longer_vec`. Before that, we need to permute the subsititution vectors.
+            // Only the bottom 6 bits of a byte are used in VPERB, so we don't even need to mask.
+            shuffled_first_subs_vec.zmm = _mm512_maskz_permutexvar_epi8(mask, longer_vec.zmm, row_first_subs_vec.zmm);
+            shuffled_second_subs_vec.zmm = _mm512_maskz_permutexvar_epi8(mask, longer_vec.zmm, row_second_subs_vec.zmm);
+            shuffled_third_subs_vec.zmm = _mm512_maskz_permutexvar_epi8(mask, longer_vec.zmm, row_third_subs_vec.zmm);
+            shuffled_fourth_subs_vec.zmm = _mm512_maskz_permutexvar_epi8(mask, longer_vec.zmm, row_fourth_subs_vec.zmm);
+
+            // To blend we can invoke three `_mm512_cmplt_epu8_mask`, but we can also achieve the same using
+            // the AND logical operation, checking the top two bits of every byte.
+            // Continuing this thought, we can use the VPTESTMB instruction to output the mask after the AND.
+            __mmask64 is_third_or_fourth = _mm512_mask_test_epi8_mask(mask, longer_vec.zmm, is_third_or_fourth_vec.zmm);
+            __mmask64 is_second_or_fourth =
+                _mm512_mask_test_epi8_mask(mask, longer_vec.zmm, is_second_or_fourth_vec.zmm);
+            lookup_substitution_vec.zmm = _mm512_mask_blend_epi8(
+                is_third_or_fourth,
+                // Choose between the first and the second.
+                _mm512_mask_blend_epi8(is_second_or_fourth, shuffled_first_subs_vec.zmm, shuffled_second_subs_vec.zmm),
+                // Choose between the third and the fourth.
+                _mm512_mask_blend_epi8(is_second_or_fourth, shuffled_third_subs_vec.zmm, shuffled_fourth_subs_vec.zmm));
+
+            // First, sign-extend lower and upper 16 bytes to 16-bit integers.
+            __m512i current_0_31_vec = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(lookup_substitution_vec.zmm, 0));
+            __m512i current_32_63_vec = _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(lookup_substitution_vec.zmm, 1));
+
+            // Now extend those 16-bit integers to 32-bit.
+            // This isn't free, same as the subsequent store, so we only want to do that for the populated lanes.
+            // To minimize the number of loads and stores, we can combine our substitution costs with the previous
+            // distances, containing the deletion costs.
+            {
+                cost_substitution_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + idx_longer);
+                cost_substitution_vec.zmm = _mm512_add_epi32(
+                    cost_substitution_vec.zmm, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(current_0_31_vec, 0)));
+                cost_deletion_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + 1 + idx_longer);
+                cost_deletion_vec.zmm = _mm512_add_epi32(cost_deletion_vec.zmm, gap_vec.zmm);
+                current_vec.zmm = _mm512_max_epi32(cost_substitution_vec.zmm, cost_deletion_vec.zmm);
+
+                // Inclusive prefix minimum computation to combine with insertion costs.
+                // Simply disabling this operation results in 5x performance improvement, meaning
+                // that this operation is responsible for 80% of the total runtime.
+                //    for (sz_size_t idx_longer = 0; idx_longer < longer_length; ++idx_longer) {
+                //        current_distances[idx_longer + 1] =
+                //            sz_max_of_two(current_distances[idx_longer] + gap, current_distances[idx_longer + 1]);
+                //    }
+                //
+                // To perform the same operation in vectorized form, we need to perform a tree-like reduction,
+                // that will involve multiple steps. It's quite expensive and should be first tested in the
+                // "experimental" section.
+                //
+                // Another approach might be loop unrolling:
+                //      current_vec.i32s[0] = last_in_row = sz_i32_max_of_two(current_vec.i32s[0], last_in_row + gap);
+                //      current_vec.i32s[1] = last_in_row = sz_i32_max_of_two(current_vec.i32s[1], last_in_row + gap);
+                //      current_vec.i32s[2] = last_in_row = sz_i32_max_of_two(current_vec.i32s[2], last_in_row + gap);
+                //      ... yet this approach is also quite expensive.
+                for (int i = 0; i != 16; ++i)
+                    current_vec.i32s[i] = last_in_row = sz_max_of_two(current_vec.i32s[i], last_in_row + gap);
+                _mm512_mask_storeu_epi32(current_distances + idx_longer + 1, mask, current_vec.zmm);
+            }
+
+            // Export the values from 16 to 31.
+            if (register_length > 16) {
+                mask = _kshiftri_mask64(mask, 16);
+                cost_substitution_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + idx_longer + 16);
+                cost_substitution_vec.zmm = _mm512_add_epi32(
+                    cost_substitution_vec.zmm, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(current_0_31_vec, 1)));
+                cost_deletion_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + 1 + idx_longer + 16);
+                cost_deletion_vec.zmm = _mm512_add_epi32(cost_deletion_vec.zmm, gap_vec.zmm);
+                current_vec.zmm = _mm512_max_epi32(cost_substitution_vec.zmm, cost_deletion_vec.zmm);
+
+                // Aggregate running insertion costs within the register.
+                for (int i = 0; i != 16; ++i)
+                    current_vec.i32s[i] = last_in_row = sz_max_of_two(current_vec.i32s[i], last_in_row + gap);
+                _mm512_mask_storeu_epi32(current_distances + idx_longer + 1 + 16, mask, current_vec.zmm);
+            }
+
+            // Export the values from 32 to 47.
+            if (register_length > 32) {
+                mask = _kshiftri_mask64(mask, 16);
+                cost_substitution_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + idx_longer + 32);
+                cost_substitution_vec.zmm = _mm512_add_epi32(
+                    cost_substitution_vec.zmm, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(current_32_63_vec, 0)));
+                cost_deletion_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + 1 + idx_longer + 32);
+                cost_deletion_vec.zmm = _mm512_add_epi32(cost_deletion_vec.zmm, gap_vec.zmm);
+                current_vec.zmm = _mm512_max_epi32(cost_substitution_vec.zmm, cost_deletion_vec.zmm);
+
+                // Aggregate running insertion costs within the register.
+                for (int i = 0; i != 16; ++i)
+                    current_vec.i32s[i] = last_in_row = sz_max_of_two(current_vec.i32s[i], last_in_row + gap);
+                _mm512_mask_storeu_epi32(current_distances + idx_longer + 1 + 32, mask, current_vec.zmm);
+            }
+
+            // Export the values from 32 to 47.
+            if (register_length > 48) {
+                mask = _kshiftri_mask64(mask, 16);
+                cost_substitution_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + idx_longer + 48);
+                cost_substitution_vec.zmm = _mm512_add_epi32(
+                    cost_substitution_vec.zmm, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(current_32_63_vec, 1)));
+                cost_deletion_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + 1 + idx_longer + 48);
+                cost_deletion_vec.zmm = _mm512_add_epi32(cost_deletion_vec.zmm, gap_vec.zmm);
+                current_vec.zmm = _mm512_max_epi32(cost_substitution_vec.zmm, cost_deletion_vec.zmm);
+
+                // Aggregate running insertion costs within the register.
+                for (int i = 0; i != 16; ++i)
+                    current_vec.i32s[i] = last_in_row = sz_max_of_two(current_vec.i32s[i], last_in_row + gap);
+                _mm512_mask_storeu_epi32(current_distances + idx_longer + 1 + 48, mask, current_vec.zmm);
+            }
+        }
+
+        // Swap previous_distances and current_distances pointers
+        sz_u64_swap((sz_u64_t *)&previous_distances, (sz_u64_t *)&current_distances);
+    }
+
+    // Cache scalar before `free` call.
+    sz_ssize_t result = previous_distances[longer_length];
+    alloc->free(distances, buffer_length, alloc->handle);
+    return result;
+}
+
+SZ_INTERNAL sz_ssize_t sz_alignment_score_avx512( //
+    sz_cptr_t shorter, sz_size_t shorter_length,  //
+    sz_cptr_t longer, sz_size_t longer_length,    //
+    sz_error_cost_t const *subs, sz_error_cost_t gap, sz_memory_allocator_t *alloc) {
+
+    if (sz_max_of_two(shorter_length, longer_length) < (256ull * 256ull * 256ull))
+        return _sz_alignment_score_wagner_fisher_upto17m_avx512(shorter, shorter_length, longer, longer_length, subs,
+                                                                gap, alloc);
+    else
+        return sz_alignment_score_serial(shorter, shorter_length, longer, longer_length, subs, gap, alloc);
+}
+
+#pragma clang attribute pop
+#pragma GCC pop_options
+#endif
+
+#pragma endregion
+
+/*  @brief  Implementation of the string search algorithms using the Arm NEON instruction set, available on 64-bit
+ *          Arm processors. Implements: {substring search, character search, character set search} x {forward, reverse}.
+ */
+#pragma region ARM NEON
+
+#if SZ_USE_ARM_NEON
+
+/**
+ *  @brief  Helper structure to simplify work with 64-bit words.
+ */
+typedef union sz_u128_vec_t {
+    uint8x16_t u8x16;
+    uint16x8_t u16x8;
+    uint32x4_t u32x4;
+    uint64x2_t u64x2;
+    sz_u64_t u64s[2];
+    sz_u32_t u32s[4];
+    sz_u16_t u16s[8];
+    sz_u8_t u8s[16];
+} sz_u128_vec_t;
+
+SZ_INTERNAL sz_u64_t vreinterpretq_u8_u4(uint8x16_t vec) {
+    // Use `vshrn` to produce a bitmask, similar to `movemask` in SSE.
+    // https://community.arm.com/arm-community-blogs/b/infrastructure-solutions-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon
+    return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vec), 4)), 0) & 0x8888888888888888ull;
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_byte_neon(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+    sz_u64_t matches;
+    sz_u128_vec_t h_vec, n_vec, matches_vec;
+    n_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)n);
+
+    while (h_length >= 16) {
+        h_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
+        matches_vec.u8x16 = vceqq_u8(h_vec.u8x16, n_vec.u8x16);
+        // In Arm NEON we don't have a `movemask` to combine it with `ctz` and get the offset of the match.
+        // But assuming the `vmaxvq` is cheap, we can use it to find the first match, by blending (bitwise selecting)
+        // the vector with a relative offsets array.
+        matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+        if (matches) return h + sz_u64_ctz(matches) / 4;
+
+        h += 16, h_length -= 16;
+    }
+
+    return sz_find_byte_serial(h, h_length, n);
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_byte_neon(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n) {
+    sz_u64_t matches;
+    sz_u128_vec_t h_vec, n_vec, matches_vec;
+    n_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)n);
+
+    while (h_length >= 16) {
+        h_vec.u8x16 = vld1q_u8((sz_u8_t const *)h + h_length - 16);
+        matches_vec.u8x16 = vceqq_u8(h_vec.u8x16, n_vec.u8x16);
+        matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+        if (matches) return h + h_length - 1 - sz_u64_clz(matches) / 4;
+        h_length -= 16;
+    }
+
+    return sz_rfind_byte_serial(h, h_length, n);
+}
+
+SZ_PUBLIC sz_u64_t _sz_find_charset_neon_register(sz_u128_vec_t h_vec, uint8x16_t set_top_vec_u8x16,
+                                                  uint8x16_t set_bottom_vec_u8x16) {
+
+    // Once we've read the characters in the haystack, we want to
+    // compare them against our bitset. The serial version of that code
+    // would look like: `(set_->_u8s[c >> 3] & (1u << (c & 7u))) != 0`.
+    uint8x16_t byte_index_vec = vshrq_n_u8(h_vec.u8x16, 3);
+    uint8x16_t byte_mask_vec = vshlq_u8(vdupq_n_u8(1), vreinterpretq_s8_u8(vandq_u8(h_vec.u8x16, vdupq_n_u8(7))));
+    uint8x16_t matches_top_vec = vqtbl1q_u8(set_top_vec_u8x16, byte_index_vec);
+    // The table lookup instruction in NEON replies to out-of-bound requests with zeros.
+    // The values in `byte_index_vec` all fall in [0; 32). So for values under 16, substracting 16 will underflow
+    // and map into interval [240, 256). Meaning that those will be populated with zeros and we can safely
+    // merge `matches_top_vec` and `matches_bottom_vec` with a bitwise OR.
+    uint8x16_t matches_bottom_vec = vqtbl1q_u8(set_bottom_vec_u8x16, vsubq_u8(byte_index_vec, vdupq_n_u8(16)));
+    uint8x16_t matches_vec = vorrq_u8(matches_top_vec, matches_bottom_vec);
+    // Istead of pure `vandq_u8`, we can immediately broadcast a match presence across each 8-bit word.
+    matches_vec = vtstq_u8(matches_vec, byte_mask_vec);
+    return vreinterpretq_u8_u4(matches_vec);
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_neon(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+    if (n_length == 1) return sz_find_byte_neon(h, h_length, n);
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Broadcast those characters into SIMD registers.
+    sz_u64_t matches;
+    sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
+    n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
+    n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
+    n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
+
+    // Scan through the string.
+    for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
+        h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
+        h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
+        h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
+        matches_vec.u8x16 = vandq_u8(                           //
+            vandq_u8(                                           //
+                vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
+                vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
+            vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
+        matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+        while (matches) {
+            int potential_offset = sz_u64_ctz(matches) / 4;
+            if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
+            matches &= matches - 1;
+        }
+    }
+
+    return sz_find_serial(h, h_length, n, n_length);
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_neon(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL;
+    if (n_length == 1) return sz_rfind_byte_neon(h, h_length, n);
+
+    // Pick the parts of the needle that are worth comparing.
+    sz_size_t offset_first, offset_mid, offset_last;
+    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+
+    // Will contain 4 bits per character.
+    sz_u64_t matches;
+    sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
+    n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
+    n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
+    n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
+
+    sz_cptr_t h_reversed;
+    for (; h_length >= n_length + 16; h_length -= 16) {
+        h_reversed = h + h_length - n_length - 16 + 1;
+        h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h_reversed + offset_first));
+        h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h_reversed + offset_mid));
+        h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h_reversed + offset_last));
+        matches_vec.u8x16 = vandq_u8(                           //
+            vandq_u8(                                           //
+                vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
+                vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
+            vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
+        matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+        while (matches) {
+            int potential_offset = sz_u64_clz(matches) / 4;
+            if (sz_equal(h + h_length - n_length - potential_offset, n, n_length))
+                return h + h_length - n_length - potential_offset;
+            sz_assert((matches & (1ull << (63 - potential_offset * 4))) != 0 &&
+                      "The bit must be set before we squash it");
+            matches &= ~(1ull << (63 - potential_offset * 4));
+        }
+    }
+
+    return sz_rfind_serial(h, h_length, n, n_length);
+}
+
+SZ_PUBLIC sz_cptr_t sz_find_charset_neon(sz_cptr_t h, sz_size_t h_length, sz_charset_t const *set) {
+    sz_u64_t matches;
+    sz_u128_vec_t h_vec;
+    uint8x16_t set_top_vec_u8x16 = vld1q_u8(&set->_u8s[0]);
+    uint8x16_t set_bottom_vec_u8x16 = vld1q_u8(&set->_u8s[16]);
+
+    for (; h_length >= 16; h += 16, h_length -= 16) {
+        h_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h));
+        matches = _sz_find_charset_neon_register(h_vec, set_top_vec_u8x16, set_bottom_vec_u8x16);
+        if (matches) return h + sz_u64_ctz(matches) / 4;
+    }
+
+    return sz_find_charset_serial(h, h_length, set);
+}
+
+SZ_PUBLIC sz_cptr_t sz_rfind_charset_neon(sz_cptr_t h, sz_size_t h_length, sz_charset_t const *set) {
+    sz_u64_t matches;
+    sz_u128_vec_t h_vec;
+    uint8x16_t set_top_vec_u8x16 = vld1q_u8(&set->_u8s[0]);
+    uint8x16_t set_bottom_vec_u8x16 = vld1q_u8(&set->_u8s[16]);
+
+    // Check `sz_find_charset_neon` for explanations.
+    for (; h_length >= 16; h_length -= 16) {
+        h_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h) + h_length - 16);
+        matches = _sz_find_charset_neon_register(h_vec, set_top_vec_u8x16, set_bottom_vec_u8x16);
+        if (matches) return h + h_length - 1 - sz_u64_clz(matches) / 4;
+    }
+
+    return sz_rfind_charset_serial(h, h_length, set);
+}
+
+#endif // Arm Neon
+
+#pragma endregion
+
+/*
+ *  @brief  Pick the right implementation for the string search algorithms.
+ */
+#pragma region Compile-Time Dispatching
+
+SZ_PUBLIC sz_u64_t sz_hash(sz_cptr_t ins, sz_size_t length) { return sz_hash_serial(ins, length); }
+SZ_PUBLIC void sz_tolower(sz_cptr_t ins, sz_size_t length, sz_ptr_t outs) { sz_tolower_serial(ins, length, outs); }
+SZ_PUBLIC void sz_toupper(sz_cptr_t ins, sz_size_t length, sz_ptr_t outs) { sz_toupper_serial(ins, length, outs); }
+SZ_PUBLIC void sz_toascii(sz_cptr_t ins, sz_size_t length, sz_ptr_t outs) { sz_toascii_serial(ins, length, outs); }
+
+SZ_PUBLIC void sz_hashes_fingerprint(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_ptr_t fingerprint,
+                                     sz_size_t fingerprint_bytes) {
+
+    sz_bool_t fingerprint_length_is_power_of_two = (sz_bool_t)((fingerprint_bytes & (fingerprint_bytes - 1)) == 0);
+    sz_string_view_t fingerprint_buffer = {fingerprint, fingerprint_bytes};
+
+    // https://blog.stuffedcow.net/2015/08/pagewalk-coherence/
+
+    // In most cases the fingerprint length will be a power of two.
+    if (fingerprint_length_is_power_of_two == sz_false_k)
+        sz_hashes(start, length, window_length, 1, _sz_hashes_fingerprint_non_pow2_callback, &fingerprint_buffer);
+    else
+        sz_hashes(start, length, window_length, 1, _sz_hashes_fingerprint_pow2_callback, &fingerprint_buffer);
+}
+
+#if !SZ_DYNAMIC_DISPATCH
+
+SZ_DYNAMIC sz_bool_t sz_equal(sz_cptr_t a, sz_cptr_t b, sz_size_t length) {
+#if SZ_USE_X86_AVX512
+    return sz_equal_avx512(a, b, length);
+#else
+    return sz_equal_serial(a, b, length);
+#endif
+}
+
+SZ_DYNAMIC sz_ordering_t sz_order(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length) {
+#if SZ_USE_X86_AVX512
+    return sz_order_avx512(a, a_length, b, b_length);
+#else
+    return sz_order_serial(a, a_length, b, b_length);
+#endif
+}
+
+SZ_DYNAMIC void sz_copy(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+#if SZ_USE_X86_AVX512
+    sz_copy_avx512(target, source, length);
+#elif SZ_USE_X86_AVX2
+    sz_copy_avx2(target, source, length);
+#else
+    sz_copy_serial(target, source, length);
+#endif
+}
+
+SZ_DYNAMIC void sz_move(sz_ptr_t target, sz_cptr_t source, sz_size_t length) {
+#if SZ_USE_X86_AVX512
+    sz_move_avx512(target, source, length);
+#elif SZ_USE_X86_AVX2
+    sz_move_avx2(target, source, length);
+#else
+    sz_move_serial(target, source, length);
+#endif
+}
+
+SZ_DYNAMIC void sz_fill(sz_ptr_t target, sz_size_t length, sz_u8_t value) {
+#if SZ_USE_X86_AVX512
+    sz_fill_avx512(target, length, value);
+#elif SZ_USE_X86_AVX2
+    sz_fill_avx2(target, length, value);
+#else
+    sz_fill_serial(target, length, value);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_byte(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle) {
+#if SZ_USE_X86_AVX512
+    return sz_find_byte_avx512(haystack, h_length, needle);
+#elif SZ_USE_X86_AVX2
+    return sz_find_byte_avx2(haystack, h_length, needle);
+#elif SZ_USE_ARM_NEON
+    return sz_find_byte_neon(haystack, h_length, needle);
+#else
+    return sz_find_byte_serial(haystack, h_length, needle);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_byte(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle) {
+#if SZ_USE_X86_AVX512
+    return sz_rfind_byte_avx512(haystack, h_length, needle);
+#elif SZ_USE_X86_AVX2
+    return sz_rfind_byte_avx2(haystack, h_length, needle);
+#elif SZ_USE_ARM_NEON
+    return sz_rfind_byte_neon(haystack, h_length, needle);
+#else
+    return sz_rfind_byte_serial(haystack, h_length, needle);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length) {
+#if SZ_USE_X86_AVX512
+    return sz_find_avx512(haystack, h_length, needle, n_length);
+#elif SZ_USE_X86_AVX2
+    return sz_find_avx2(haystack, h_length, needle, n_length);
+#elif SZ_USE_ARM_NEON
+    return sz_find_neon(haystack, h_length, needle, n_length);
+#else
+    return sz_find_serial(haystack, h_length, needle, n_length);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind(sz_cptr_t haystack, sz_size_t h_length, sz_cptr_t needle, sz_size_t n_length) {
+#if SZ_USE_X86_AVX512
+    return sz_rfind_avx512(haystack, h_length, needle, n_length);
+#elif SZ_USE_X86_AVX2
+    return sz_rfind_avx2(haystack, h_length, needle, n_length);
+#elif SZ_USE_ARM_NEON
+    return sz_rfind_neon(haystack, h_length, needle, n_length);
+#else
+    return sz_rfind_serial(haystack, h_length, needle, n_length);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_charset(sz_cptr_t text, sz_size_t length, sz_charset_t const *set) {
+#if SZ_USE_X86_AVX512
+    return sz_find_charset_avx512(text, length, set);
+#elif SZ_USE_ARM_NEON
+    return sz_find_charset_neon(text, length, set);
+#else
+    return sz_find_charset_serial(text, length, set);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_charset(sz_cptr_t text, sz_size_t length, sz_charset_t const *set) {
+#if SZ_USE_X86_AVX512
+    return sz_rfind_charset_avx512(text, length, set);
+#elif SZ_USE_ARM_NEON
+    return sz_rfind_charset_neon(text, length, set);
+#else
+    return sz_rfind_charset_serial(text, length, set);
+#endif
+}
+
+SZ_DYNAMIC sz_size_t sz_edit_distance( //
+    sz_cptr_t a, sz_size_t a_length,   //
+    sz_cptr_t b, sz_size_t b_length,   //
+    sz_size_t bound, sz_memory_allocator_t *alloc) {
+#if SZ_USE_X86_AVX512
+    return sz_edit_distance_avx512(a, a_length, b, b_length, bound, alloc);
+#else
+    return sz_edit_distance_serial(a, a_length, b, b_length, bound, alloc);
+#endif
+}
+
+SZ_DYNAMIC sz_ssize_t sz_alignment_score(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
+                                         sz_error_cost_t const *subs, sz_error_cost_t gap,
+                                         sz_memory_allocator_t *alloc) {
+#if SZ_USE_X86_AVX512
+    return sz_alignment_score_avx512(a, a_length, b, b_length, subs, gap, alloc);
+#else
+    return sz_alignment_score_serial(a, a_length, b, b_length, subs, gap, alloc);
+#endif
+}
+
+SZ_DYNAMIC void sz_hashes(sz_cptr_t text, sz_size_t length, sz_size_t window_length, sz_size_t window_step, //
+                          sz_hash_callback_t callback, void *callback_handle) {
+#if SZ_USE_X86_AVX512
+    sz_hashes_avx512(text, length, window_length, window_step, callback, callback_handle);
+#elif SZ_USE_X86_AVX2
+    sz_hashes_avx2(text, length, window_length, window_step, callback, callback_handle);
+#else
+    sz_hashes_serial(text, length, window_length, window_step, callback, callback_handle);
+#endif
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_char_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    return sz_find_charset(h, h_length, &set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_find_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    sz_charset_invert(&set);
+    return sz_find_charset(h, h_length, &set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_char_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    return sz_rfind_charset(h, h_length, &set);
+}
+
+SZ_DYNAMIC sz_cptr_t sz_rfind_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+    sz_charset_t set;
+    sz_charset_init(&set);
+    for (; n_length; ++n, --n_length) sz_charset_add(&set, *n);
+    sz_charset_invert(&set);
+    return sz_rfind_charset(h, h_length, &set);
+}
+
+#endif
+#pragma endregion
+
+#ifdef __cplusplus
+#pragma GCC diagnostic pop
+}
+#endif // __cplusplus
+
+#endif // STRINGZILLA_H_
diff --git a/include/stringzilla/stringzilla.hpp b/include/stringzilla/stringzilla.hpp
new file mode 100644
index 00000000..47c0e8ec
--- /dev/null
+++ b/include/stringzilla/stringzilla.hpp
@@ -0,0 +1,3755 @@
+/**
+ *  @brief  StringZilla C++ wrapper improving over the performance of `std::string_view` and `std::string`,
+ *          mostly for substring search, adding approximate matching functionality, and C++23 functionality
+ *          to a C++11 compatible implementation.
+ *
+ *  This implementation is aiming to be compatible with C++11, while implementing the C++23 functionality.
+ *  By default, it includes C++ STL headers, but that can be avoided to minimize compilation overhead.
+ *  https://artificial-mind.net/projects/compile-health/
+ *
+ *  @see    StringZilla: https://github.com/ashvardanian/StringZilla/blob/main/README.md
+ *  @see    C++ Standard String: https://en.cppreference.com/w/cpp/header/string
+ *
+ *  @file   stringzilla.hpp
+ *  @author Ash Vardanian
+ */
+#ifndef STRINGZILLA_HPP_
+#define STRINGZILLA_HPP_
+
+/**
+ *  @brief  When set to 1, the library will include the C++ STL headers and implement
+ *          automatic conversion from and to `std::stirng_view` and `std::basic_string<any_allocator>`.
+ */
+#ifndef SZ_AVOID_STL
+#define SZ_AVOID_STL (0) // true or false
+#endif
+
+/*  We need to detect the version of the C++ language we are compiled with.
+ *  This will affect recent features like `operator<=>` and tests against STL.
+ */
+#define SZ_DETECT_CPP_23 (__cplusplus >= 202101L)
+#define SZ_DETECT_CPP20 (__cplusplus >= 202002L)
+#define SZ_DETECT_CPP_17 (__cplusplus >= 201703L)
+#define SZ_DETECT_CPP14 (__cplusplus >= 201402L)
+#define SZ_DETECT_CPP_11 (__cplusplus >= 201103L)
+#define SZ_DETECT_CPP_98 (__cplusplus >= 199711L)
+
+/**
+ *  @brief  The `constexpr` keyword has different applicability scope in different C++ versions.
+ *          Useful for STL conversion operators, as several `std::string` members are `constexpr` in C++20.
+ */
+#if SZ_DETECT_CPP20
+#define sz_constexpr_if_cpp20 constexpr
+#else
+#define sz_constexpr_if_cpp20
+#endif
+
+#if !SZ_AVOID_STL
+#include <bitset>
+#include <string>
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+#include <string_view>
+#endif
+#endif
+
+#include <cassert>   // `assert`
+#include <cstddef>   // `std::size_t`
+#include <iosfwd>    // `std::basic_ostream`
+#include <stdexcept> // `std::out_of_range`
+#include <utility>   // `std::swap`
+
+#include <stringzilla/stringzilla.h>
+
+namespace ashvardanian {
+namespace stringzilla {
+
+template <typename>
+class basic_charset;
+template <typename>
+class basic_string_slice;
+template <typename, typename>
+class basic_string;
+
+using string_span = basic_string_slice<char>;
+using string_view = basic_string_slice<char const>;
+
+template <std::size_t count_characters>
+using carray = char[count_characters];
+
+#pragma region Character Sets
+
+/**
+ *  @brief  The concatenation of the `ascii_lowercase` and `ascii_uppercase`. This value is not locale-dependent.
+ *          https://docs.python.org/3/library/string.html#string.ascii_letters
+ */
+inline carray<52> const &ascii_letters() noexcept {
+    static carray<52> const all = {
+        //
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
+        's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+        'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+    };
+    return all;
+}
+
+/**
+ *  @brief  The lowercase letters "abcdefghijklmnopqrstuvwxyz". This value is not locale-dependent.
+ *          https://docs.python.org/3/library/string.html#string.ascii_lowercase
+ */
+inline carray<26> const &ascii_lowercase() noexcept {
+    static carray<26> const all = {
+        //
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+    };
+    return all;
+}
+
+/**
+ *  @brief  The uppercase letters "ABCDEFGHIJKLMNOPQRSTUVWXYZ". This value is not locale-dependent.
+ *          https://docs.python.org/3/library/string.html#string.ascii_uppercase
+ */
+inline carray<26> const &ascii_uppercase() noexcept {
+    static carray<26> const all = {
+        //
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+    };
+    return all;
+}
+
+/**
+ *  @brief  ASCII characters which are considered printable.
+ *          A combination of `digits`, `ascii_letters`, `punctuation`, and `whitespace`.
+ *          https://docs.python.org/3/library/string.html#string.printable
+ */
+inline carray<100> const &ascii_printables() noexcept {
+    static carray<100> const all = {
+        //
+        '0', '1', '2', '3', '4', '5',  '6', '7', '8',  '9', 'a', 'b', 'c', 'd', 'e', 'f',  'g',  'h',  'i',  'j',
+        'k', 'l', 'm', 'n', 'o', 'p',  'q', 'r', 's',  't', 'u', 'v', 'w', 'x', 'y', 'z',  'A',  'B',  'C',  'D',
+        'E', 'F', 'G', 'H', 'I', 'J',  'K', 'L', 'M',  'N', 'O', 'P', 'Q', 'R', 'S', 'T',  'U',  'V',  'W',  'X',
+        'Y', 'Z', '!', '"', '#', '$',  '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.',  '/',  ':',  ';',  '<',
+        '=', '>', '?', '@', '[', '\\', ']', '^', '_',  '`', '{', '|', '}', '~', ' ', '\t', '\n', '\r', '\f', '\v',
+    };
+    return all;
+}
+
+/**
+ *  @brief  Non-printable ASCII control characters.
+ *          Includes all codes from 0 to 31 and 127.
+ */
+inline carray<33> const &ascii_controls() noexcept {
+    static carray<33> const all = {
+        //
+        0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,  16,
+        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127,
+    };
+    return all;
+}
+
+/**
+ *  @brief  The digits "0123456789".
+ *          https://docs.python.org/3/library/string.html#string.digits
+ */
+inline carray<10> const &digits() noexcept {
+    static carray<10> const all = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+    return all;
+}
+
+/**
+ *  @brief  The letters "0123456789abcdefABCDEF".
+ *          https://docs.python.org/3/library/string.html#string.hexdigits
+ */
+inline carray<22> const &hexdigits() noexcept {
+    static carray<22> const all = {
+        //
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
+        'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F',
+    };
+    return all;
+}
+
+/**
+ *  @brief  The letters "01234567".
+ *          https://docs.python.org/3/library/string.html#string.octdigits
+ */
+inline carray<8> const &octdigits() noexcept {
+    static carray<8> const all = {'0', '1', '2', '3', '4', '5', '6', '7'};
+    return all;
+}
+
+/**
+ *  @brief  ASCII characters considered punctuation characters in the C locale:
+ *          !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~.
+ *          https://docs.python.org/3/library/string.html#string.punctuation
+ */
+inline carray<32> const &punctuation() noexcept {
+    static carray<32> const all = {
+        //
+        '!', '"', '#', '$', '%', '&', '\'', '(',  ')', '*', '+', ',', '-', '.', '/', ':',
+        ';', '<', '=', '>', '?', '@', '[',  '\\', ']', '^', '_', '`', '{', '|', '}', '~',
+    };
+    return all;
+}
+
+/**
+ *  @brief  ASCII characters that are considered whitespace.
+ *          This includes space, tab, linefeed, return, formfeed, and vertical tab.
+ *          https://docs.python.org/3/library/string.html#string.whitespace
+ */
+inline carray<6> const &whitespaces() noexcept {
+    static carray<6> const all = {' ', '\t', '\n', '\r', '\f', '\v'};
+    return all;
+}
+
+/**
+ *  @brief  ASCII characters that are considered line delimiters.
+ *          https://docs.python.org/3/library/stdtypes.html#str.splitlines
+ */
+inline carray<8> const &newlines() noexcept {
+    static carray<8> const all = {'\n', '\r', '\f', '\v', '\x1C', '\x1D', '\x1E', '\x85'};
+    return all;
+}
+
+/**
+ *  @brief  ASCII characters forming the BASE64 encoding alphabet.
+ */
+inline carray<64> const &base64() noexcept {
+    static carray<64> const all = {
+        //
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+        'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
+        'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+    };
+    return all;
+}
+
+/**
+ *  @brief  A set of characters represented as a bitset with 256 slots.
+ */
+template <typename char_type_ = char>
+class basic_charset {
+    sz_charset_t bitset_;
+
+  public:
+    using char_type = char_type_;
+
+    basic_charset() noexcept {
+        // ! Instead of relying on the `sz_charset_init`, we have to reimplement it to support `constexpr`.
+        bitset_._u64s[0] = 0, bitset_._u64s[1] = 0, bitset_._u64s[2] = 0, bitset_._u64s[3] = 0;
+    }
+    explicit basic_charset(std::initializer_list<char_type> chars) noexcept : basic_charset() {
+        // ! Instead of relying on the `sz_charset_add(&bitset_, c)`, we have to reimplement it to support `constexpr`.
+        for (auto c : chars) bitset_._u64s[sz_bitcast(sz_u8_t, c) >> 6] |= (1ull << (sz_bitcast(sz_u8_t, c) & 63u));
+    }
+    template <std::size_t count_characters>
+    explicit basic_charset(char_type const (&chars)[count_characters]) noexcept : basic_charset() {
+        static_assert(count_characters > 0, "Character array cannot be empty");
+        for (std::size_t i = 0; i < count_characters - 1; ++i) { // count_characters - 1 to exclude the null terminator
+            char_type c = chars[i];
+            bitset_._u64s[sz_bitcast(sz_u8_t, c) >> 6] |= (1ull << (sz_bitcast(sz_u8_t, c) & 63u));
+        }
+    }
+
+    template <std::size_t count_characters>
+    explicit basic_charset(std::array<char_type, count_characters> const &chars) noexcept : basic_charset() {
+        static_assert(count_characters > 0, "Character array cannot be empty");
+        for (std::size_t i = 0; i < count_characters - 1; ++i) { // count_characters - 1 to exclude the null terminator
+            char_type c = chars[i];
+            bitset_._u64s[sz_bitcast(sz_u8_t, c) >> 6] |= (1ull << (sz_bitcast(sz_u8_t, c) & 63u));
+        }
+    }
+
+    basic_charset(basic_charset const &other) noexcept : bitset_(other.bitset_) {}
+    basic_charset &operator=(basic_charset const &other) noexcept {
+        bitset_ = other.bitset_;
+        return *this;
+    }
+
+    basic_charset operator|(basic_charset other) const noexcept {
+        basic_charset result = *this;
+        result.bitset_._u64s[0] |= other.bitset_._u64s[0], result.bitset_._u64s[1] |= other.bitset_._u64s[1],
+            result.bitset_._u64s[2] |= other.bitset_._u64s[2], result.bitset_._u64s[3] |= other.bitset_._u64s[3];
+        return *this;
+    }
+
+    inline basic_charset &add(char_type c) noexcept {
+        sz_charset_add(&bitset_, sz_bitcast(sz_u8_t, c));
+        return *this;
+    }
+    inline sz_charset_t &raw() noexcept { return bitset_; }
+    inline sz_charset_t const &raw() const noexcept { return bitset_; }
+    inline bool contains(char_type c) const noexcept { return sz_charset_contains(&bitset_, sz_bitcast(sz_u8_t, c)); }
+    inline basic_charset inverted() const noexcept {
+        basic_charset result = *this;
+        sz_charset_invert(&result.bitset_);
+        return result;
+    }
+};
+
+using char_set = basic_charset<char>;
+
+inline char_set ascii_letters_set() { return char_set {ascii_letters()}; }
+inline char_set ascii_lowercase_set() { return char_set {ascii_lowercase()}; }
+inline char_set ascii_uppercase_set() { return char_set {ascii_uppercase()}; }
+inline char_set ascii_printables_set() { return char_set {ascii_printables()}; }
+inline char_set ascii_controls_set() { return char_set {ascii_controls()}; }
+inline char_set digits_set() { return char_set {digits()}; }
+inline char_set hexdigits_set() { return char_set {hexdigits()}; }
+inline char_set octdigits_set() { return char_set {octdigits()}; }
+inline char_set punctuation_set() { return char_set {punctuation()}; }
+inline char_set whitespaces_set() { return char_set {whitespaces()}; }
+inline char_set newlines_set() { return char_set {newlines()}; }
+inline char_set base64_set() { return char_set {base64()}; }
+
+#pragma endregion
+
+#pragma region Ranges of Search Matches
+
+struct end_sentinel_type {};
+struct include_overlaps_type {};
+struct exclude_overlaps_type {};
+
+#if SZ_DETECT_CPP_17
+inline static constexpr end_sentinel_type end_sentinel;
+inline static constexpr include_overlaps_type include_overlaps;
+inline static constexpr exclude_overlaps_type exclude_overlaps;
+#endif
+
+/**
+ *  @brief  Zero-cost wrapper around the `.find` member function of string-like classes.
+ */
+template <typename string_type_, typename overlaps_type = include_overlaps_type>
+struct matcher_find {
+    using size_type = typename string_type_::size_type;
+    string_type_ needle_;
+
+    matcher_find(string_type_ needle = {}) noexcept : needle_(needle) {}
+    size_type needle_length() const noexcept { return needle_.length(); }
+    size_type operator()(string_type_ haystack) const noexcept { return haystack.find(needle_); }
+    size_type skip_length() const noexcept {
+        // TODO: Apply Galil rule to match repetitive patterns in strictly linear time.
+        return std::is_same<overlaps_type, include_overlaps_type>() ? 1 : needle_.length();
+    }
+};
+
+/**
+ *  @brief  Zero-cost wrapper around the `.rfind` member function of string-like classes.
+ */
+template <typename string_type_, typename overlaps_type = include_overlaps_type>
+struct matcher_rfind {
+    using size_type = typename string_type_::size_type;
+    string_type_ needle_;
+
+    matcher_rfind(string_type_ needle = {}) noexcept : needle_(needle) {}
+    size_type needle_length() const noexcept { return needle_.length(); }
+    size_type operator()(string_type_ haystack) const noexcept { return haystack.rfind(needle_); }
+    size_type skip_length() const noexcept {
+        // TODO: Apply Galil rule to match repetitive patterns in strictly linear time.
+        return std::is_same<overlaps_type, include_overlaps_type>() ? 1 : needle_.length();
+    }
+};
+
+/**
+ *  @brief  Zero-cost wrapper around the `.find_first_of` member function of string-like classes.
+ */
+template <typename haystack_type, typename needles_type = haystack_type>
+struct matcher_find_first_of {
+    using size_type = typename haystack_type::size_type;
+    needles_type needles_;
+    constexpr size_type needle_length() const noexcept { return 1; }
+    constexpr size_type skip_length() const noexcept { return 1; }
+    size_type operator()(haystack_type haystack) const noexcept { return haystack.find_first_of(needles_); }
+};
+
+/**
+ *  @brief  Zero-cost wrapper around the `.find_last_of` member function of string-like classes.
+ */
+template <typename haystack_type, typename needles_type = haystack_type>
+struct matcher_find_last_of {
+    using size_type = typename haystack_type::size_type;
+    needles_type needles_;
+    constexpr size_type needle_length() const noexcept { return 1; }
+    constexpr size_type skip_length() const noexcept { return 1; }
+    size_type operator()(haystack_type haystack) const noexcept { return haystack.find_last_of(needles_); }
+};
+
+/**
+ *  @brief  Zero-cost wrapper around the `.find_first_not_of` member function of string-like classes.
+ */
+template <typename haystack_type, typename needles_type = haystack_type>
+struct matcher_find_first_not_of {
+    using size_type = typename haystack_type::size_type;
+    needles_type needles_;
+    constexpr size_type needle_length() const noexcept { return 1; }
+    constexpr size_type skip_length() const noexcept { return 1; }
+    size_type operator()(haystack_type haystack) const noexcept { return haystack.find_first_not_of(needles_); }
+};
+
+/**
+ *  @brief  Zero-cost wrapper around the `.find_last_not_of` member function of string-like classes.
+ */
+template <typename haystack_type, typename needles_type = haystack_type>
+struct matcher_find_last_not_of {
+    using size_type = typename haystack_type::size_type;
+    needles_type needles_;
+    constexpr size_type needle_length() const noexcept { return 1; }
+    constexpr size_type skip_length() const noexcept { return 1; }
+    size_type operator()(haystack_type haystack) const noexcept { return haystack.find_last_not_of(needles_); }
+};
+
+/**
+ *  @brief  A range of string slices representing the matches of a substring search.
+ *          Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
+ *          Similar to a pair of `boost::algorithm::find_iterator`.
+ */
+template <typename string_type_, typename matcher_type_>
+class range_matches {
+  public:
+    using string_type = string_type_;
+    using matcher_type = matcher_type_;
+
+  private:
+    matcher_type matcher_;
+    string_type haystack_;
+
+  public:
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+    using value_type = string_type;
+    using pointer = string_type;   // Needed for compatibility with STL container constructors.
+    using reference = string_type; // Needed for compatibility with STL container constructors.
+
+    range_matches(string_type haystack, matcher_type needle) noexcept : matcher_(needle), haystack_(haystack) {}
+
+    class iterator {
+        matcher_type matcher_;
+        string_type remaining_;
+
+      public:
+        using iterator_category = std::forward_iterator_tag;
+        using difference_type = std::ptrdiff_t;
+        using value_type = string_type;
+        using pointer = string_type;   // Needed for compatibility with STL container constructors.
+        using reference = string_type; // Needed for compatibility with STL container constructors.
+
+        iterator(string_type haystack, matcher_type matcher) noexcept : matcher_(matcher), remaining_(haystack) {
+            auto position = matcher_(remaining_);
+            remaining_.remove_prefix(position != string_type::npos ? position : remaining_.size());
+        }
+
+        pointer operator->() const noexcept = delete;
+        value_type operator*() const noexcept { return remaining_.substr(0, matcher_.needle_length()); }
+
+        iterator &operator++() noexcept {
+            remaining_.remove_prefix(matcher_.skip_length());
+            auto position = matcher_(remaining_);
+            remaining_.remove_prefix(position != string_type::npos ? position : remaining_.size());
+            return *this;
+        }
+
+        iterator operator++(int) noexcept {
+            iterator temp = *this;
+            ++(*this);
+            return temp;
+        }
+
+        bool operator!=(iterator const &other) const noexcept { return remaining_.begin() != other.remaining_.begin(); }
+        bool operator==(iterator const &other) const noexcept { return remaining_.begin() == other.remaining_.begin(); }
+        bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty(); }
+        bool operator==(end_sentinel_type) const noexcept { return remaining_.empty(); }
+    };
+
+    iterator begin() const noexcept { return {haystack_, matcher_}; }
+    iterator end() const noexcept { return {string_type {haystack_.data() + haystack_.size(), 0ull}, matcher_}; }
+    size_type size() const noexcept { return static_cast<size_type>(ssize()); }
+    difference_type ssize() const noexcept { return std::distance(begin(), end()); }
+    bool empty() const noexcept { return begin() == end_sentinel_type {}; }
+    bool include_overlaps() const noexcept { return matcher_.skip_length() < matcher_.needle_length(); }
+
+    /**
+     *  @brief  Copies the matches into a container.
+     */
+    template <typename container_>
+    void to(container_ &container) {
+        for (auto match : *this) { container.push_back(match); }
+    }
+
+    /**
+     *  @brief  Copies the matches into a consumed container, returning it at the end.
+     */
+    template <typename container_>
+    container_ to() {
+        return container_ {begin(), end()};
+    }
+};
+
+/**
+ *  @brief  A range of string slices representing the matches of a @b reverse-order substring search.
+ *          Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
+ *          Similar to a pair of `boost::algorithm::find_iterator`.
+ */
+template <typename string_type_, typename matcher_type_>
+class range_rmatches {
+  public:
+    using string_type = string_type_;
+    using matcher_type = matcher_type_;
+
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+    using value_type = string_type;
+    using pointer = string_type;   // Needed for compatibility with STL container constructors.
+    using reference = string_type; // Needed for compatibility with STL container constructors.
+
+  private:
+    matcher_type matcher_;
+    string_type haystack_;
+
+  public:
+    range_rmatches(string_type haystack, matcher_type needle) : matcher_(needle), haystack_(haystack) {}
+
+    class iterator {
+        matcher_type matcher_;
+        string_type remaining_;
+
+      public:
+        using iterator_category = std::forward_iterator_tag;
+        using difference_type = std::ptrdiff_t;
+        using value_type = string_type;
+        using pointer = string_type;   // Needed for compatibility with STL container constructors.
+        using reference = string_type; // Needed for compatibility with STL container constructors.
+
+        iterator(string_type haystack, matcher_type matcher) noexcept : matcher_(matcher), remaining_(haystack) {
+            auto position = matcher_(remaining_);
+            remaining_.remove_suffix(position != string_type::npos
+                                         ? remaining_.size() - position - matcher_.needle_length()
+                                         : remaining_.size());
+        }
+
+        pointer operator->() const noexcept = delete;
+        value_type operator*() const noexcept {
+            return remaining_.substr(remaining_.size() - matcher_.needle_length());
+        }
+
+        iterator &operator++() noexcept {
+            remaining_.remove_suffix(matcher_.skip_length());
+            auto position = matcher_(remaining_);
+            remaining_.remove_suffix(position != string_type::npos
+                                         ? remaining_.size() - position - matcher_.needle_length()
+                                         : remaining_.size());
+            return *this;
+        }
+
+        iterator operator++(int) noexcept {
+            iterator temp = *this;
+            ++(*this);
+            return temp;
+        }
+
+        bool operator!=(iterator const &other) const noexcept { return remaining_.end() != other.remaining_.end(); }
+        bool operator==(iterator const &other) const noexcept { return remaining_.end() == other.remaining_.end(); }
+        bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty(); }
+        bool operator==(end_sentinel_type) const noexcept { return remaining_.empty(); }
+    };
+
+    iterator begin() const noexcept { return {haystack_, matcher_}; }
+    iterator end() const noexcept { return {string_type {haystack_.data(), 0ull}, matcher_}; }
+    size_type size() const noexcept { return static_cast<size_type>(ssize()); }
+    difference_type ssize() const noexcept { return std::distance(begin(), end()); }
+    bool empty() const noexcept { return begin() == end_sentinel_type {}; }
+    bool include_overlaps() const noexcept { return matcher_.skip_length() < matcher_.needle_length(); }
+
+    /**
+     *  @brief  Copies the matches into a container.
+     */
+    template <typename container_>
+    void to(container_ &container) {
+        for (auto match : *this) { container.push_back(match); }
+    }
+
+    /**
+     *  @brief  Copies the matches into a consumed container, returning it at the end.
+     */
+    template <typename container_>
+    container_ to() {
+        return container_ {begin(), end()};
+    }
+};
+
+/**
+ *  @brief  A range of string slices for different splits of the data.
+ *          Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
+ *          Similar to a pair of `boost::algorithm::split_iterator`.
+ *
+ *  In some sense, represents the inverse operation to `range_matches`, as it reports not the search matches
+ *  but the data between them. Meaning that for `N` search matches, there will be `N+1` elements in the range.
+ *  Unlike ::range_matches, this range can't be empty. It also can't report overlapping intervals.
+ */
+template <typename string_type_, typename matcher_type_>
+class range_splits {
+  public:
+    using string_type = string_type_;
+    using matcher_type = matcher_type_;
+
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+    using value_type = string_type;
+    using pointer = string_type;   // Needed for compatibility with STL container constructors.
+    using reference = string_type; // Needed for compatibility with STL container constructors.
+
+  private:
+    matcher_type matcher_;
+    string_type haystack_;
+
+  public:
+    range_splits(string_type haystack, matcher_type needle) noexcept : matcher_(needle), haystack_(haystack) {}
+
+    class iterator {
+        matcher_type matcher_;
+        string_type remaining_;
+        std::size_t length_within_remaining_;
+        bool reached_tail_;
+
+      public:
+        using iterator_category = std::forward_iterator_tag;
+        using difference_type = std::ptrdiff_t;
+        using value_type = string_type;
+        using pointer = string_type;   // Needed for compatibility with STL container constructors.
+        using reference = string_type; // Needed for compatibility with STL container constructors.
+
+        iterator(string_type haystack, matcher_type matcher) noexcept : matcher_(matcher), remaining_(haystack) {
+            auto position = matcher_(remaining_);
+            length_within_remaining_ = position != string_type::npos ? position : remaining_.size();
+            reached_tail_ = false;
+        }
+
+        iterator(string_type haystack, matcher_type matcher, end_sentinel_type) noexcept
+            : matcher_(matcher), remaining_(haystack), length_within_remaining_(0), reached_tail_(true) {}
+
+        pointer operator->() const noexcept = delete;
+        value_type operator*() const noexcept { return remaining_.substr(0, length_within_remaining_); }
+
+        iterator &operator++() noexcept {
+            remaining_.remove_prefix(length_within_remaining_);
+            reached_tail_ = remaining_.empty();
+            remaining_.remove_prefix(matcher_.needle_length() * !reached_tail_);
+            auto position = matcher_(remaining_);
+            length_within_remaining_ = position != string_type::npos ? position : remaining_.size();
+            return *this;
+        }
+
+        iterator operator++(int) noexcept {
+            iterator temp = *this;
+            ++(*this);
+            return temp;
+        }
+
+        bool operator!=(iterator const &other) const noexcept {
+            return (remaining_.begin() != other.remaining_.begin()) || (reached_tail_ != other.reached_tail_);
+        }
+        bool operator==(iterator const &other) const noexcept {
+            return (remaining_.begin() == other.remaining_.begin()) && (reached_tail_ == other.reached_tail_);
+        }
+        bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty() || !reached_tail_; }
+        bool operator==(end_sentinel_type) const noexcept { return remaining_.empty() && reached_tail_; }
+        bool is_last() const noexcept { return remaining_.size() == length_within_remaining_; }
+    };
+
+    iterator begin() const noexcept { return {haystack_, matcher_}; }
+    iterator end() const noexcept { return {string_type {haystack_.end(), 0}, matcher_, end_sentinel_type {}}; }
+    size_type size() const noexcept { return static_cast<size_type>(ssize()); }
+    difference_type ssize() const noexcept { return std::distance(begin(), end()); }
+    constexpr bool empty() const noexcept { return false; }
+
+    /**
+     *  @brief  Copies the matches into a container.
+     */
+    template <typename container_>
+    void to(container_ &container) {
+        for (auto match : *this) { container.push_back(match); }
+    }
+
+    /**
+     *  @brief  Copies the matches into a consumed container, returning it at the end.
+     */
+    template <typename container_>
+    container_ to(container_ &&container = {}) {
+        for (auto match : *this) { container.push_back(match); }
+        return std::move(container);
+    }
+};
+
+/**
+ *  @brief  A range of string slices for different splits of the data in @b reverse-order.
+ *          Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
+ *          Similar to a pair of `boost::algorithm::split_iterator`.
+ *
+ *  In some sense, represents the inverse operation to `range_matches`, as it reports not the search matches
+ *  but the data between them. Meaning that for `N` search matches, there will be `N+1` elements in the range.
+ *  Unlike ::range_matches, this range can't be empty. It also can't report overlapping intervals.
+ */
+template <typename string_type_, typename matcher_type_>
+class range_rsplits {
+  public:
+    using string_type = string_type_;
+    using matcher_type = matcher_type_;
+
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+    using value_type = string_type;
+    using pointer = string_type;   // Needed for compatibility with STL container constructors.
+    using reference = string_type; // Needed for compatibility with STL container constructors.
+
+  private:
+    matcher_type matcher_;
+    string_type haystack_;
+
+  public:
+    range_rsplits(string_type haystack, matcher_type needle) noexcept : matcher_(needle), haystack_(haystack) {}
+
+    class iterator {
+        matcher_type matcher_;
+        string_type remaining_;
+        std::size_t length_within_remaining_;
+        bool reached_tail_;
+
+      public:
+        using iterator_category = std::forward_iterator_tag;
+        using difference_type = std::ptrdiff_t;
+        using value_type = string_type;
+        using pointer = string_type;   // Needed for compatibility with STL container constructors.
+        using reference = string_type; // Needed for compatibility with STL container constructors.
+
+        iterator(string_type haystack, matcher_type matcher) noexcept : matcher_(matcher), remaining_(haystack) {
+            auto position = matcher_(remaining_);
+            length_within_remaining_ = position != string_type::npos
+                                           ? remaining_.size() - position - matcher_.needle_length()
+                                           : remaining_.size();
+            reached_tail_ = false;
+        }
+
+        iterator(string_type haystack, matcher_type matcher, end_sentinel_type) noexcept
+            : matcher_(matcher), remaining_(haystack), length_within_remaining_(0), reached_tail_(true) {}
+
+        pointer operator->() const noexcept = delete;
+        value_type operator*() const noexcept {
+            return remaining_.substr(remaining_.size() - length_within_remaining_);
+        }
+
+        iterator &operator++() noexcept {
+            remaining_.remove_suffix(length_within_remaining_);
+            reached_tail_ = remaining_.empty();
+            remaining_.remove_suffix(matcher_.needle_length() * !reached_tail_);
+            auto position = matcher_(remaining_);
+            length_within_remaining_ = position != string_type::npos
+                                           ? remaining_.size() - position - matcher_.needle_length()
+                                           : remaining_.size();
+            return *this;
+        }
+
+        iterator operator++(int) noexcept {
+            iterator temp = *this;
+            ++(*this);
+            return temp;
+        }
+
+        bool operator!=(iterator const &other) const noexcept {
+            return (remaining_.end() != other.remaining_.end()) || (reached_tail_ != other.reached_tail_);
+        }
+        bool operator==(iterator const &other) const noexcept {
+            return (remaining_.end() == other.remaining_.end()) && (reached_tail_ == other.reached_tail_);
+        }
+        bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty() || !reached_tail_; }
+        bool operator==(end_sentinel_type) const noexcept { return remaining_.empty() && reached_tail_; }
+        bool is_last() const noexcept { return remaining_.size() == length_within_remaining_; }
+    };
+
+    iterator begin() const noexcept { return {haystack_, matcher_}; }
+    iterator end() const noexcept { return {{haystack_.data(), 0ull}, matcher_, end_sentinel_type {}}; }
+    size_type size() const noexcept { return static_cast<size_type>(ssize()); }
+    difference_type ssize() const noexcept { return std::distance(begin(), end()); }
+    constexpr bool empty() const noexcept { return false; }
+
+    /**
+     *  @brief  Copies the matches into a container.
+     */
+    template <typename container_>
+    void to(container_ &container) {
+        for (auto match : *this) { container.push_back(match); }
+    }
+
+    /**
+     *  @brief  Copies the matches into a consumed container, returning it at the end.
+     */
+    template <typename container_>
+    container_ to(container_ &&container = {}) {
+        for (auto match : *this) { container.push_back(match); }
+        return std::move(container);
+    }
+};
+
+/**
+ *  @brief  Find all potentially @b overlapping inclusions of a needle substring.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_matches<string, matcher_find<string, include_overlaps_type>> find_all(string const &h, string const &n,
+                                                                            include_overlaps_type = {}) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all potentially @b overlapping inclusions of a needle substring in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rmatches<string, matcher_rfind<string, include_overlaps_type>> rfind_all(string const &h, string const &n,
+                                                                               include_overlaps_type = {}) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all @b non-overlapping inclusions of a needle substring.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_matches<string, matcher_find<string, exclude_overlaps_type>> find_all(string const &h, string const &n,
+                                                                            exclude_overlaps_type) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all @b non-overlapping inclusions of a needle substring in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rmatches<string, matcher_rfind<string, exclude_overlaps_type>> rfind_all(string const &h, string const &n,
+                                                                               exclude_overlaps_type) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all inclusions of characters from the second string.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_matches<string, matcher_find_first_of<string>> find_all_characters(string const &h, string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all inclusions of characters from the second string in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rmatches<string, matcher_find_last_of<string>> rfind_all_characters(string const &h, string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all characters except the ones in the second string.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_matches<string, matcher_find_first_not_of<string>> find_all_other_characters(string const &h,
+                                                                                   string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Find all characters except the ones in the second string in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rmatches<string, matcher_find_last_not_of<string>> rfind_all_other_characters(string const &h,
+                                                                                    string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Splits a string around every @b non-overlapping inclusion of the second string.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_splits<string, matcher_find<string, exclude_overlaps_type>> split(string const &h, string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Splits a string around every @b non-overlapping inclusion of the second string in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rsplits<string, matcher_rfind<string, exclude_overlaps_type>> rsplit(string const &h, string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Splits a string around every character from the second string.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_splits<string, matcher_find_first_of<string>> split_characters(string const &h, string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Splits a string around every character from the second string in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rsplits<string, matcher_find_last_of<string>> rsplit_characters(string const &h, string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Splits a string around every character except the ones from the second string.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_splits<string, matcher_find_first_not_of<string>> split_other_characters(string const &h,
+                                                                               string const &n) noexcept {
+    return {h, n};
+}
+
+/**
+ *  @brief  Splits a string around every character except the ones from the second string in @b reverse order.
+ *  @tparam string  A string-like type, ideally a view, like StringZilla or STL `string_view`.
+ */
+template <typename string>
+range_rsplits<string, matcher_find_last_not_of<string>> rsplit_other_characters(string const &h,
+                                                                                string const &n) noexcept {
+    return {h, n};
+}
+
+/**  @brief  Helper function using `std::advance` iterator and return it back. */
+template <typename iterator_type, typename distance_type>
+iterator_type advanced(iterator_type &&it, distance_type n) {
+    std::advance(it, n);
+    return it;
+}
+
+/**  @brief  Helper function using `range_length` to compute the unsigned distance. */
+template <typename iterator_type>
+std::size_t range_length(iterator_type first, iterator_type last) {
+    return static_cast<std::size_t>(std::distance(first, last));
+}
+
+#pragma endregion
+
+#pragma region Global Operations with Dynamic Memory
+
+template <typename allocator_type_>
+static void *_call_allocate(sz_size_t n, void *allocator_state) noexcept {
+    return reinterpret_cast<allocator_type_ *>(allocator_state)->allocate(n);
+}
+
+template <typename allocator_type_>
+static void _call_free(void *ptr, sz_size_t n, void *allocator_state) noexcept {
+    return reinterpret_cast<allocator_type_ *>(allocator_state)->deallocate(reinterpret_cast<char *>(ptr), n);
+}
+
+template <typename generator_type_>
+static sz_u64_t _call_random_generator(void *state) noexcept {
+    generator_type_ &generator = *reinterpret_cast<generator_type_ *>(state);
+    return generator();
+}
+
+template <typename allocator_type_, typename allocator_callback_>
+static bool _with_alloc(allocator_type_ &allocator, allocator_callback_ &&callback) noexcept {
+    sz_memory_allocator_t alloc;
+    alloc.allocate = &_call_allocate<allocator_type_>;
+    alloc.free = &_call_free<allocator_type_>;
+    alloc.handle = &allocator;
+    return callback(alloc);
+}
+
+template <typename allocator_type_, typename allocator_callback_>
+static bool _with_alloc(allocator_callback_ &&callback) noexcept {
+    allocator_type_ allocator;
+    return _with_alloc(allocator, std::forward<allocator_callback_>(callback));
+}
+
+#pragma endregion
+
+#pragma region Helper Template Classes
+
+/**
+ *  @brief  A result of split a string once, containing the string slice ::before,
+ *          the ::match itself, and the slice ::after.
+ */
+template <typename string_>
+struct string_partition_result {
+    string_ before;
+    string_ match;
+    string_ after;
+};
+
+/**
+ *  @brief  A reverse iterator for mutable and immutable character buffers.
+ *          Replaces `std::reverse_iterator` to avoid including `<iterator>`.
+ */
+template <typename value_type_>
+class reversed_iterator_for {
+  public:
+    using iterator_category = std::random_access_iterator_tag;
+    using value_type = value_type_;
+    using difference_type = std::ptrdiff_t;
+    using pointer = value_type_ *;
+    using reference = value_type_ &;
+
+    reversed_iterator_for(pointer ptr) noexcept : ptr_(ptr) {}
+    reference operator*() const noexcept { return *ptr_; }
+
+    bool operator==(reversed_iterator_for const &other) const noexcept { return ptr_ == other.ptr_; }
+    bool operator!=(reversed_iterator_for const &other) const noexcept { return ptr_ != other.ptr_; }
+    reference operator[](difference_type n) const noexcept { return *(*this + n); }
+    reversed_iterator_for operator+(difference_type n) const noexcept { return reversed_iterator_for(ptr_ - n); }
+    reversed_iterator_for operator-(difference_type n) const noexcept { return reversed_iterator_for(ptr_ + n); }
+    difference_type operator-(reversed_iterator_for const &other) const noexcept { return other.ptr_ - ptr_; }
+
+    reversed_iterator_for &operator++() noexcept {
+        --ptr_;
+        return *this;
+    }
+
+    reversed_iterator_for operator++(int) const noexcept {
+        reversed_iterator_for temp = *this;
+        --ptr_;
+        return temp;
+    }
+
+    reversed_iterator_for &operator--() const noexcept {
+        ++ptr_;
+        return *this;
+    }
+
+    reversed_iterator_for operator--(int) const noexcept {
+        reversed_iterator_for temp = *this;
+        ++ptr_;
+        return temp;
+    }
+
+  private:
+    value_type_ *ptr_;
+};
+
+/**
+ *  @brief  An "expression template" for lazy concatenation of strings using the `operator|`.
+ *
+ *  TODO: Ensure eqnership passing and move semantics are preserved.
+ */
+template <typename first_type, typename second_type>
+struct concatenation {
+
+    using value_type = typename first_type::value_type;
+    using pointer = value_type *;
+    using const_pointer = value_type const *;
+    using size_type = typename first_type::size_type;
+    using difference_type = typename first_type::difference_type;
+
+    first_type first;
+    second_type second;
+
+    std::size_t size() const noexcept { return first.size() + second.size(); }
+    std::size_t length() const noexcept { return first.size() + second.size(); }
+
+    size_type copy(pointer destination) const noexcept {
+        first.copy(destination);
+        second.copy(destination + first.length());
+        return first.length() + second.length();
+    }
+
+    size_type copy(pointer destination, size_type length) const noexcept {
+        auto first_length = std::min(first.length(), length);
+        auto second_length = std::min(second.length(), length - first_length);
+        first.copy(destination, first_length);
+        second.copy(destination + first_length, second_length);
+        return first_length + second_length;
+    }
+
+    template <typename last_type>
+    concatenation<concatenation<first_type, second_type>, last_type> operator|(last_type &&last) const {
+        return {*this, last};
+    }
+};
+
+#pragma endregion
+
+#pragma region String Views/Spans
+
+/**
+ *  @brief  A string slice (view/span) class implementing a superset of C++23 functionality
+ *          with much faster SIMD-accelerated substring search and approximate matching.
+ *          Constructors are `constexpr` enabling `_sz` literals.
+ *
+ *  @tparam char_type_  The character type, usually `char const` or `char`. Must be a single byte long.
+ */
+template <typename char_type_>
+class basic_string_slice {
+
+    static_assert(sizeof(char_type_) == 1, "Characters must be a single byte long");
+    static_assert(std::is_reference<char_type_>::value == false, "Characters can't be references");
+
+    using char_type = char_type_;
+    using mutable_char_type = typename std::remove_const<char_type_>::type;
+    using immutable_char_type = typename std::add_const<char_type_>::type;
+
+    char_type *start_;
+    std::size_t length_;
+
+  public:
+    // STL compatibility
+    using traits_type = std::char_traits<char_type_>;
+    using value_type = mutable_char_type;
+    using pointer = char_type *;
+    using const_pointer = immutable_char_type *;
+    using reference = char_type &;
+    using const_reference = immutable_char_type &;
+    using const_iterator = const_pointer;
+    using iterator = pointer;
+    using reverse_iterator = reversed_iterator_for<char_type>;
+    using const_reverse_iterator = reversed_iterator_for<immutable_char_type>;
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+
+    // Non-STL type definitions
+    using string_slice = basic_string_slice<char_type>;
+    using string_span = basic_string_slice<mutable_char_type>;
+    using string_view = basic_string_slice<immutable_char_type>;
+    using partition_type = string_partition_result<string_slice>;
+
+    /** @brief  Special value for missing matches.
+     *          We take the largest 63-bit unsigned integer.
+     */
+    static constexpr size_type npos = 0x7FFFFFFFFFFFFFFFull;
+
+#pragma region Constructors and STL Utilities
+
+    constexpr basic_string_slice() noexcept : start_(nullptr), length_(0) {}
+    constexpr basic_string_slice(pointer c_string) noexcept
+        : start_(c_string), length_(null_terminated_length(c_string)) {}
+    constexpr basic_string_slice(pointer c_string, size_type length) noexcept : start_(c_string), length_(length) {}
+
+    sz_constexpr_if_cpp20 basic_string_slice(basic_string_slice const &other) noexcept = default;
+    sz_constexpr_if_cpp20 basic_string_slice &operator=(basic_string_slice const &other) noexcept = default;
+    basic_string_slice(std::nullptr_t) = delete;
+
+    /**  @brief Exchanges the view with that of the `other`. */
+    void swap(string_slice &other) noexcept { std::swap(start_, other.start_), std::swap(length_, other.length_); }
+
+#if !SZ_AVOID_STL
+
+    template <typename sfinae_ = char_type, typename std::enable_if<std::is_const<sfinae_>::value, int>::type = 0>
+    sz_constexpr_if_cpp20 basic_string_slice(std::string const &other) noexcept
+        : basic_string_slice(other.data(), other.size()) {}
+
+    template <typename sfinae_ = char_type, typename std::enable_if<!std::is_const<sfinae_>::value, int>::type = 0>
+    sz_constexpr_if_cpp20 basic_string_slice(std::string &other) noexcept
+        : basic_string_slice(&other[0], other.size()) {} // The `.data()` has mutable variant only since C++17
+
+    template <typename sfinae_ = char_type, typename std::enable_if<std::is_const<sfinae_>::value, int>::type = 0>
+    sz_constexpr_if_cpp20 string_slice &operator=(std::string const &other) noexcept {
+        return assign({other.data(), other.size()});
+    }
+
+    template <typename sfinae_ = char_type, typename std::enable_if<!std::is_const<sfinae_>::value, int>::type = 0>
+    sz_constexpr_if_cpp20 string_slice &operator=(std::string &other) noexcept {
+        return assign({other.data(), other.size()});
+    }
+
+    operator std::string() const { return {data(), size()}; }
+
+    /**
+     *  @brief  Formatted output function for compatibility with STL's `std::basic_ostream`.
+     *  @throw  `std::ios_base::failure` if an exception occurred during output.
+     */
+    template <typename stream_traits>
+    friend std::basic_ostream<value_type, stream_traits> &operator<<(std::basic_ostream<value_type, stream_traits> &os,
+                                                                     string_slice const &str) noexcept(false) {
+        return os.write(str.data(), str.size());
+    }
+
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+
+    template <typename sfinae_ = char_type, typename std::enable_if<std::is_const<sfinae_>::value, int>::type = 0>
+    sz_constexpr_if_cpp20 basic_string_slice(std::string_view const &other) noexcept
+        : basic_string_slice(other.data(), other.size()) {}
+
+    template <typename sfinae_ = char_type, typename std::enable_if<std::is_const<sfinae_>::value, int>::type = 0>
+    sz_constexpr_if_cpp20 string_slice &operator=(std::string_view const &other) noexcept {
+        return assign({other.data(), other.size()});
+    }
+    operator std::string_view() const noexcept { return {data(), size()}; }
+
+#endif
+
+#endif
+
+#pragma endregion
+
+#pragma region Iterators and Element Access
+
+    iterator begin() const noexcept { return iterator(start_); }
+    iterator end() const noexcept { return iterator(start_ + length_); }
+    const_iterator cbegin() const noexcept { return const_iterator(start_); }
+    const_iterator cend() const noexcept { return const_iterator(start_ + length_); }
+    reverse_iterator rbegin() const noexcept { return reverse_iterator(start_ + length_ - 1); }
+    reverse_iterator rend() const noexcept { return reverse_iterator(start_ - 1); }
+    const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(start_ + length_ - 1); }
+    const_reverse_iterator crend() const noexcept { return const_reverse_iterator(start_ - 1); }
+
+    reference operator[](size_type pos) const noexcept { return start_[pos]; }
+    reference at(size_type pos) const noexcept { return start_[pos]; }
+    reference front() const noexcept { return start_[0]; }
+    reference back() const noexcept { return start_[length_ - 1]; }
+    pointer data() const noexcept { return start_; }
+
+    difference_type ssize() const noexcept { return static_cast<difference_type>(length_); }
+    size_type size() const noexcept { return length_; }
+    size_type length() const noexcept { return length_; }
+    size_type max_size() const noexcept { return npos - 1; }
+    bool empty() const noexcept { return length_ == 0; }
+
+#pragma endregion
+
+#pragma region Slicing
+
+#pragma region Safe and Signed Extensions
+
+    /**
+     *  @brief  Equivalent to Python's `"abc"[-3:-1]`. Exception-safe, unlike STL's `substr`.
+     *          Supports signed and unsigned intervals.
+     */
+    string_slice operator[](std::initializer_list<difference_type> signed_offsets) const noexcept {
+        assert(signed_offsets.size() == 2 && "operator[] can't take more than 2 offsets");
+        return sub(signed_offsets.begin()[0], signed_offsets.begin()[1]);
+    }
+
+    /**
+     *  @brief  Signed alternative to `at()`. Handy if you often write `str[str.size() - 2]`.
+     *  @warning The behavior is @b undefined if the position is beyond bounds.
+     */
+    reference sat(difference_type signed_offset) const noexcept {
+        size_type pos = (signed_offset < 0) ? size() + signed_offset : signed_offset;
+        assert(pos < size() && "string_slice::sat(i) out of bounds");
+        return start_[pos];
+    }
+
+    /**
+     *  @brief  The opposite operation to `remove_prefix`, that does no bounds checking.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    string_slice front(size_type n) const noexcept {
+        assert(n <= size() && "string_slice::front(n) out of bounds");
+        return {start_, n};
+    }
+
+    /**
+     *  @brief  The opposite operation to `remove_prefix`, that does no bounds checking.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    string_slice back(size_type n) const noexcept {
+        assert(n <= size() && "string_slice::back(n) out of bounds");
+        return {start_ + length_ - n, n};
+    }
+
+    /**
+     *  @brief  Equivalent to Python's `"abc"[-3:-1]`. Exception-safe, unlike STL's `substr`.
+     *          Supports signed and unsigned intervals.
+     */
+    string_slice sub(difference_type signed_start_offset, difference_type signed_end_offset = npos) const noexcept {
+        sz_size_t normalized_offset, normalized_length;
+        sz_ssize_clamp_interval(length_, signed_start_offset, signed_end_offset, &normalized_offset,
+                                &normalized_length);
+        return string_slice(start_ + normalized_offset, normalized_length);
+    }
+
+    /**
+     *  @brief  Exports this entire view. Not an STL function, but useful for concatenations.
+     *          The STL variant expects at least two arguments.
+     */
+    size_type copy(value_type *destination) const noexcept {
+        sz_copy((sz_ptr_t)destination, start_, length_);
+        return length_;
+    }
+
+#pragma endregion
+
+#pragma region STL Style
+
+    /**
+     *  @brief  Removes the first `n` characters from the view.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    void remove_prefix(size_type n) noexcept { assert(n <= size()), start_ += n, length_ -= n; }
+
+    /**
+     *  @brief  Removes the last `n` characters from the view.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    void remove_suffix(size_type n) noexcept { assert(n <= size()), length_ -= n; }
+
+    /**  @brief  Added for STL compatibility. */
+    string_slice substr() const noexcept { return *this; }
+
+    /**
+     *  @brief  Return a slice of this view after first `skip` bytes.
+     *  @throws `std::out_of_range` if `skip > size()`.
+     *  @see    `sub` for a cleaner exception-less alternative.
+     */
+    string_slice substr(size_type skip) const noexcept(false) {
+        if (skip > size()) throw std::out_of_range("string_slice::substr");
+        return string_slice(start_ + skip, length_ - skip);
+    }
+
+    /**
+     *  @brief  Return a slice of this view after first `skip` bytes, taking at most `count` bytes.
+     *  @throws `std::out_of_range` if `skip > size()`.
+     *  @see    `sub` for a cleaner exception-less alternative.
+     */
+    string_slice substr(size_type skip, size_type count) const noexcept(false) {
+        if (skip > size()) throw std::out_of_range("string_slice::substr");
+        return string_slice(start_ + skip, sz_min_of_two(count, length_ - skip));
+    }
+
+    /**
+     *  @brief  Exports a slice of this view after first `skip` bytes, taking at most `count` bytes.
+     *  @throws `std::out_of_range` if `skip > size()`.
+     *  @see    `sub` for a cleaner exception-less alternative.
+     */
+    size_type copy(value_type *destination, size_type count, size_type skip = 0) const noexcept(false) {
+        if (skip > size()) throw std::out_of_range("string_slice::copy");
+        count = sz_min_of_two(count, length_ - skip);
+        sz_copy((sz_ptr_t)destination, start_ + skip, count);
+        return count;
+    }
+
+#pragma endregion
+
+#pragma endregion
+
+#pragma region Comparisons
+
+#pragma region Whole String Comparisons
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     */
+    int compare(string_view other) const noexcept {
+        return (int)sz_order(start_, length_, other.start_, other.length_);
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to `substr(pos1, count1).compare(other)`.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()`.
+     */
+    int compare(size_type pos1, size_type count1, string_view other) const noexcept(false) {
+        return substr(pos1, count1).compare(other);
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to `substr(pos1, count1).compare(other.substr(pos2, count2))`.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()` or if `pos2 > other.size()`.
+     */
+    int compare(size_type pos1, size_type count1, string_view other, size_type pos2, size_type count2) const
+        noexcept(false) {
+        return substr(pos1, count1).compare(other.substr(pos2, count2));
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     */
+    int compare(const_pointer other) const noexcept { return compare(string_view(other)); }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to substr(pos1, count1).compare(other).
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()`.
+     */
+    int compare(size_type pos1, size_type count1, const_pointer other) const noexcept(false) {
+        return substr(pos1, count1).compare(string_view(other));
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to `substr(pos1, count1).compare({s, count2})`.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()`.
+     */
+    int compare(size_type pos1, size_type count1, const_pointer other, size_type count2) const noexcept(false) {
+        return substr(pos1, count1).compare(string_view(other, count2));
+    }
+
+    /**  @brief  Checks if the string is equal to the other string. */
+    bool operator==(string_view other) const noexcept {
+        return size() == other.size() && sz_equal(data(), other.data(), other.size()) == sz_true_k;
+    }
+
+    /**  @brief  Checks if the string is equal to a concatenation of two strings. */
+    bool operator==(concatenation<string_view, string_view> const &other) const noexcept {
+        return size() == other.size() && sz_equal(data(), other.first.data(), other.first.size()) == sz_true_k &&
+               sz_equal(data() + other.first.size(), other.second.data(), other.second.size()) == sz_true_k;
+    }
+
+#if SZ_DETECT_CPP20
+
+    /**  @brief  Computes the lexicographic ordering between this and the ::other string. */
+    std::strong_ordering operator<=>(string_view other) const noexcept {
+        std::strong_ordering orders[3] {std::strong_ordering::less, std::strong_ordering::equal,
+                                        std::strong_ordering::greater};
+        return orders[compare(other) + 1];
+    }
+
+#else
+
+    /**  @brief  Checks if the string is not equal to the other string. */
+    bool operator!=(string_view other) const noexcept { return !operator==(other); }
+
+    /**  @brief  Checks if the string is lexicographically smaller than the other string. */
+    bool operator<(string_view other) const noexcept { return compare(other) == sz_less_k; }
+
+    /**  @brief  Checks if the string is lexicographically equal or smaller than the other string. */
+    bool operator<=(string_view other) const noexcept { return compare(other) != sz_greater_k; }
+
+    /**  @brief  Checks if the string is lexicographically greater than the other string. */
+    bool operator>(string_view other) const noexcept { return compare(other) == sz_greater_k; }
+
+    /**  @brief  Checks if the string is lexicographically equal or greater than the other string. */
+    bool operator>=(string_view other) const noexcept { return compare(other) != sz_less_k; }
+
+#endif
+
+#pragma endregion
+#pragma region Prefix and Suffix Comparisons
+
+    /**  @brief  Checks if the string starts with the other string. */
+    bool starts_with(string_view other) const noexcept {
+        return length_ >= other.length_ && sz_equal(start_, other.start_, other.length_) == sz_true_k;
+    }
+
+    /**  @brief  Checks if the string starts with the other string. */
+    bool starts_with(const_pointer other) const noexcept {
+        auto other_length = null_terminated_length(other);
+        return length_ >= other_length && sz_equal(start_, other, other_length) == sz_true_k;
+    }
+
+    /**  @brief  Checks if the string starts with the other character. */
+    bool starts_with(value_type other) const noexcept { return length_ && start_[0] == other; }
+
+    /**  @brief  Checks if the string ends with the other string. */
+    bool ends_with(string_view other) const noexcept {
+        return length_ >= other.length_ &&
+               sz_equal(start_ + length_ - other.length_, other.start_, other.length_) == sz_true_k;
+    }
+
+    /**  @brief  Checks if the string ends with the other string. */
+    bool ends_with(const_pointer other) const noexcept {
+        auto other_length = null_terminated_length(other);
+        return length_ >= other_length && sz_equal(start_ + length_ - other_length, other, other_length) == sz_true_k;
+    }
+
+    /**  @brief  Checks if the string ends with the other character. */
+    bool ends_with(value_type other) const noexcept { return length_ && start_[length_ - 1] == other; }
+
+    /**  @brief  Python-like convenience function, dropping the matching prefix. */
+    string_slice remove_prefix(string_view other) const noexcept {
+        return starts_with(other) ? string_slice {start_ + other.length_, length_ - other.length_} : *this;
+    }
+
+    /**  @brief  Python-like convenience function, dropping the matching suffix. */
+    string_slice remove_suffix(string_view other) const noexcept {
+        return ends_with(other) ? string_slice {start_, length_ - other.length_} : *this;
+    }
+
+#pragma endregion
+#pragma endregion
+
+#pragma region Matching Substrings
+
+    bool contains(string_view other) const noexcept { return find(other) != npos; }
+    bool contains(value_type character) const noexcept { return find(character) != npos; }
+    bool contains(const_pointer other) const noexcept { return find(other) != npos; }
+
+#pragma region Returning offsets
+
+    /**
+     *  @brief  Find the first occurrence of a substring, skipping the first `skip` characters.
+     *          The behavior is @b undefined if `skip > size()`.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type find(string_view other, size_type skip = 0) const noexcept {
+        auto ptr = sz_find(start_ + skip, length_ - skip, other.start_, other.length_);
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a character, skipping the first `skip` characters.
+     *          The behavior is @b undefined if `skip > size()`.
+     *  @return The offset of the match, or `npos` if not found.
+     */
+    size_type find(value_type character, size_type skip = 0) const noexcept {
+        auto ptr = sz_find_byte(start_ + skip, length_ - skip, &character);
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a substring, skipping the first `skip` characters.
+     *          The behavior is @b undefined if `skip > size()`.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type find(const_pointer other, size_type pos, size_type count) const noexcept {
+        return find(string_view(other, count), pos);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a substring.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type rfind(string_view other) const noexcept {
+        auto ptr = sz_rfind(start_, length_, other.start_, other.length_);
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a substring, within first `until` characters.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type rfind(string_view other, size_type until) const noexcept(false) {
+        return until + other.size() < length_ ? substr(0, until + other.size()).rfind(other) : rfind(other);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character.
+     *  @return The offset of the match, or `npos` if not found.
+     */
+    size_type rfind(value_type character) const noexcept {
+        auto ptr = sz_rfind_byte(start_, length_, &character);
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character, within first `until` characters.
+     *  @return The offset of the match, or `npos` if not found.
+     */
+    size_type rfind(value_type character, size_type until) const noexcept {
+        return until < length_ ? substr(0, until + 1).rfind(character) : rfind(character);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a substring, within first `until` characters.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type rfind(const_pointer other, size_type until, size_type count) const noexcept {
+        return rfind(string_view(other, count), until);
+    }
+
+    /**  @brief  Find the first occurrence of a character from a set. */
+    size_type find(char_set set) const noexcept { return find_first_of(set); }
+
+    /**  @brief  Find the last occurrence of a character from a set. */
+    size_type rfind(char_set set) const noexcept { return find_last_of(set); }
+
+#pragma endregion
+#pragma region Returning Partitions
+
+    /**  @brief  Split the string into three parts, before the match, the match itself, and after it. */
+    partition_type partition(string_view pattern) const noexcept { return partition_(pattern, pattern.length()); }
+
+    /**  @brief  Split the string into three parts, before the match, the match itself, and after it. */
+    partition_type partition(char_set pattern) const noexcept { return partition_(pattern, 1); }
+
+    /**  @brief  Split the string into three parts, before the @b last match, the last match itself, and after it. */
+    partition_type rpartition(string_view pattern) const noexcept { return rpartition_(pattern, pattern.length()); }
+
+    /**  @brief  Split the string into three parts, before the @b last match, the last match itself, and after it. */
+    partition_type rpartition(char_set pattern) const noexcept { return rpartition_(pattern, 1); }
+
+#pragma endregion
+#pragma endregion
+
+#pragma region Matching Character Sets
+
+    // `isascii` is a macro in MSVC headers
+    bool contains_only(char_set set) const noexcept { return find_first_not_of(set) == npos; }
+    bool is_alpha() const noexcept { return !empty() && contains_only(ascii_letters_set()); }
+    bool is_alnum() const noexcept { return !empty() && contains_only(ascii_letters_set() | digits_set()); }
+    bool is_ascii() const noexcept { return empty() || contains_only(ascii_controls_set() | ascii_printables_set()); }
+    bool is_digit() const noexcept { return !empty() && contains_only(digits_set()); }
+    bool is_lower() const noexcept { return !empty() && contains_only(ascii_lowercase_set()); }
+    bool is_space() const noexcept { return !empty() && contains_only(whitespaces_set()); }
+    bool is_upper() const noexcept { return !empty() && contains_only(ascii_uppercase_set()); }
+    bool is_printable() const noexcept { return empty() || contains_only(ascii_printables_set()); }
+
+#pragma region Character Set Arguments
+    /**
+     *  @brief  Find the first occurrence of a character from a set.
+     *  @param  skip Number of characters to skip before the search.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_of(char_set set, size_type skip = 0) const noexcept {
+        auto ptr = sz_find_charset(start_ + skip, length_ - skip, &set.raw());
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a character outside a set.
+     *  @param  skip  The number of first characters to be skipped.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_not_of(char_set set, size_type skip = 0) const noexcept {
+        return find_first_of(set.inverted(), skip);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a set.
+     */
+    size_type find_last_of(char_set set) const noexcept {
+        auto ptr = sz_rfind_charset(start_, length_, &set.raw());
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a set.
+     */
+    size_type find_last_not_of(char_set set) const noexcept { return find_last_of(set.inverted()); }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_of(char_set set, size_type until) const noexcept {
+        auto len = sz_min_of_two(until + 1, length_);
+        auto ptr = sz_rfind_charset(start_, len, &set.raw());
+        return ptr ? ptr - start_ : npos;
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_not_of(char_set set, size_type until) const noexcept {
+        return find_last_of(set.inverted(), until);
+    }
+
+#pragma endregion
+#pragma region String Arguments
+
+    /**
+     *  @brief  Find the first occurrence of a character from a ::set.
+     *  @param  skip  The number of first characters to be skipped.
+     */
+    size_type find_first_of(string_view other, size_type skip = 0) const noexcept {
+        return find_first_of(other.as_set(), skip);
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a character outside a ::set.
+     *  @param  skip  The number of first characters to be skipped.
+     */
+    size_type find_first_not_of(string_view other, size_type skip = 0) const noexcept {
+        return find_first_not_of(other.as_set(), skip);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a ::set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_of(string_view other, size_type until = npos) const noexcept {
+        return find_last_of(other.as_set(), until);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a ::set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_not_of(string_view other, size_type until = npos) const noexcept {
+        return find_last_not_of(other.as_set(), until);
+    }
+
+#pragma endregion
+#pragma region C-Style Arguments
+
+    /**
+     *  @brief  Find the first occurrence of a character from a set.
+     *  @param  skip  The number of first characters to be skipped.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_of(const_pointer other, size_type skip, size_type count) const noexcept {
+        return find_first_of(string_view(other, count), skip);
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a character outside a set.
+     *  @param  skip  The number of first characters to be skipped.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_not_of(const_pointer other, size_type skip, size_type count) const noexcept {
+        return find_first_not_of(string_view(other, count), skip);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a set.
+     *  @param  until  The number of first characters to be considered.
+     */
+    size_type find_last_of(const_pointer other, size_type until, size_type count) const noexcept {
+        return find_last_of(string_view(other, count), until);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a set.
+     *  @param  until  The number of first characters to be considered.
+     */
+    size_type find_last_not_of(const_pointer other, size_type until, size_type count) const noexcept {
+        return find_last_not_of(string_view(other, count), until);
+    }
+
+#pragma endregion
+#pragma region Slicing
+
+    /**
+     *  @brief  Python-like convenience function, dropping prefix formed of given characters.
+     *          Similar to `boost::algorithm::trim_left_if(str, is_any_of(set))`.
+     */
+    string_slice lstrip(char_set set) const noexcept {
+        set = set.inverted();
+        auto new_start = sz_find_charset(start_, length_, &set.raw());
+        return new_start ? string_slice {new_start, length_ - static_cast<size_type>(new_start - start_)}
+                         : string_slice();
+    }
+
+    /**
+     *  @brief  Python-like convenience function, dropping suffix formed of given characters.
+     *          Similar to `boost::algorithm::trim_right_if(str, is_any_of(set))`.
+     */
+    string_slice rstrip(char_set set) const noexcept {
+        set = set.inverted();
+        auto new_end = sz_rfind_charset(start_, length_, &set.raw());
+        return new_end ? string_slice {start_, static_cast<size_type>(new_end - start_ + 1)} : string_slice();
+    }
+
+    /**
+     *  @brief  Python-like convenience function, dropping both the prefix & the suffix formed of given characters.
+     *          Similar to `boost::algorithm::trim_if(str, is_any_of(set))`.
+     */
+    string_slice strip(char_set set) const noexcept {
+        set = set.inverted();
+        auto new_start = sz_find_charset(start_, length_, &set.raw());
+        return new_start ? string_slice {new_start,
+                                         static_cast<size_type>(
+                                             sz_rfind_charset(new_start, length_ - (new_start - start_), &set.raw()) -
+                                             new_start + 1)}
+                         : string_slice();
+    }
+
+#pragma endregion
+#pragma endregion
+
+#pragma region Search Ranges
+
+    using find_all_type = range_matches<string_slice, matcher_find<string_view, include_overlaps_type>>;
+    using rfind_all_type = range_rmatches<string_slice, matcher_rfind<string_view, include_overlaps_type>>;
+
+    using find_disjoint_type = range_matches<string_slice, matcher_find<string_view, exclude_overlaps_type>>;
+    using rfind_disjoint_type = range_rmatches<string_slice, matcher_rfind<string_view, exclude_overlaps_type>>;
+
+    using find_all_chars_type = range_matches<string_slice, matcher_find_first_of<string_view, char_set>>;
+    using rfind_all_chars_type = range_rmatches<string_slice, matcher_find_last_of<string_view, char_set>>;
+
+    /**  @brief  Find all potentially @b overlapping occurrences of a given string. */
+    find_all_type find_all(string_view needle, include_overlaps_type = {}) const noexcept { return {*this, needle}; }
+
+    /**  @brief  Find all potentially @b overlapping occurrences of a given string in @b reverse order. */
+    rfind_all_type rfind_all(string_view needle, include_overlaps_type = {}) const noexcept { return {*this, needle}; }
+
+    /**  @brief  Find all @b non-overlapping occurrences of a given string. */
+    find_disjoint_type find_all(string_view needle, exclude_overlaps_type) const noexcept { return {*this, needle}; }
+
+    /**  @brief  Find all @b non-overlapping occurrences of a given string in @b reverse order. */
+    rfind_disjoint_type rfind_all(string_view needle, exclude_overlaps_type) const noexcept { return {*this, needle}; }
+
+    /**  @brief  Find all occurrences of given characters. */
+    find_all_chars_type find_all(char_set set) const noexcept { return {*this, {set}}; }
+
+    /**  @brief  Find all occurrences of given characters in @b reverse order. */
+    rfind_all_chars_type rfind_all(char_set set) const noexcept { return {*this, {set}}; }
+
+    using split_type = range_splits<string_slice, matcher_find<string_view, exclude_overlaps_type>>;
+    using rsplit_type = range_rsplits<string_slice, matcher_rfind<string_view, exclude_overlaps_type>>;
+
+    using split_chars_type = range_splits<string_slice, matcher_find_first_of<string_view, char_set>>;
+    using rsplit_chars_type = range_rsplits<string_slice, matcher_find_last_of<string_view, char_set>>;
+
+    /**  @brief  Split around occurrences of a given string. */
+    split_type split(string_view delimiter) const noexcept { return {*this, delimiter}; }
+
+    /**  @brief  Split around occurrences of a given string in @b reverse order. */
+    rsplit_type rsplit(string_view delimiter) const noexcept { return {*this, delimiter}; }
+
+    /**  @brief  Split around occurrences of given characters. */
+    split_chars_type split(char_set set = whitespaces_set()) const noexcept { return {*this, {set}}; }
+
+    /**  @brief  Split around occurrences of given characters in @b reverse order. */
+    rsplit_chars_type rsplit(char_set set = whitespaces_set()) const noexcept { return {*this, {set}}; }
+
+    /**  @brief  Split around the occurrences of all newline characters. */
+    split_chars_type splitlines() const noexcept { return split(newlines_set); }
+
+#pragma endregion
+
+    /**  @brief  Hashes the string, equivalent to `std::hash<string_view>{}(str)`. */
+    size_type hash() const noexcept { return static_cast<size_type>(sz_hash(start_, length_)); }
+
+    /**  @brief  Populate a character set with characters present in this string. */
+    char_set as_set() const noexcept {
+        char_set set;
+        for (auto c : *this) set.add(c);
+        return set;
+    }
+
+  private:
+    sz_constexpr_if_cpp20 string_view &assign(string_view const &other) noexcept {
+        start_ = other.start_;
+        length_ = other.length_;
+        return *this;
+    }
+
+    sz_constexpr_if_cpp20 static size_type null_terminated_length(const_pointer s) noexcept {
+        const_pointer p = s;
+        while (*p) ++p;
+        return p - s;
+    }
+
+    template <typename pattern_>
+    partition_type partition_(pattern_ &&pattern, std::size_t pattern_length) const noexcept {
+        size_type pos = find(pattern);
+        if (pos == npos) return {*this, string_view(), string_view()};
+        return {string_view(start_, pos), string_view(start_ + pos, pattern_length),
+                string_view(start_ + pos + pattern_length, length_ - pos - pattern_length)};
+    }
+
+    template <typename pattern_>
+    partition_type rpartition_(pattern_ &&pattern, std::size_t pattern_length) const noexcept {
+        size_type pos = rfind(pattern);
+        if (pos == npos) return {*this, string_view(), string_view()};
+        return {string_view(start_, pos), string_view(start_ + pos, pattern_length),
+                string_view(start_ + pos + pattern_length, length_ - pos - pattern_length)};
+    }
+};
+
+#pragma endregion
+
+/**
+ *  @brief  Memory-owning string class with a Small String Optimization.
+ *
+ *  @section API
+ *
+ *  Some APIs are different from `basic_string_slice`:
+ *      * `lstrip`, `rstrip`, `strip` modify the string in-place, instead of returning a new view.
+ *      * `sat`, `sub`, and element access has non-const overloads returning references to mutable objects.
+ *
+ *  Functions defined for `basic_string`, but not present in `basic_string_slice`:
+ *      * `replace`, `insert`, `erase`, `append`, `push_back`, `pop_back`, `resize`, `shrink_to_fit`... from STL,
+ *      * `try_` exception-free "try" operations that returning non-zero values on succces,
+ *      * `replace_all` and `erase_all` similar to Boost,
+ *      * `edit_distance` - Levenshtein distance computation reusing the allocator,
+ *      * `randomize`, `random` - for fast random string generation.
+ *
+ *  Functions defined for `basic_string_slice`, but not present in `basic_string`:
+ *      * `[r]partition`, `[r]split`, `[r]find_all` missing to enforce lifetime on long operations.
+ *      * `remove_prefix`, `remove_suffix` for now.
+ *
+ *  @section Exceptions
+ *
+ *  Default constructor is `constexpr`. Move constructor and move assignment operator are `noexcept`.
+ *  Copy constructor and copy assignment operator are not! They may throw `std::bad_alloc` if the memory
+ *  allocation fails. Similar to STL `std::out_of_range` if the position argument to some of the functions
+ *  is out of bounds. Same as with STL, the bound checks are often assymetric, so pay attention to docs.
+ *  If exceptions are disabled, on failure, `std::terminate` is called.
+ */
+template <typename char_type_, typename allocator_type_ = std::allocator<char_type_>>
+class basic_string {
+
+    static_assert(sizeof(char_type_) == 1, "Characters must be a single byte long");
+    static_assert(std::is_reference<char_type_>::value == false, "Characters can't be references");
+    static_assert(std::is_const<char_type_>::value == false, "Characters must be mutable");
+
+    using char_type = char_type_;
+    using sz_alloc_type = sz_memory_allocator_t;
+
+    sz_string_t string_;
+
+    /**
+     *  Stateful allocators and their support in C++ strings is extremely error-prone by design.
+     *  Depending on traits like `propagate_on_container_copy_assignment` and `propagate_on_container_move_assignment`,
+     *  its state will be copied from one string to another. It goes against the design of most string constructors,
+     *  as they also receive allocator as the last argument!
+     */
+    static_assert(std::is_empty<allocator_type_>::value, "We currently only support stateless allocators");
+
+    template <typename allocator_callback>
+    static bool _with_alloc(allocator_callback &&callback) noexcept {
+        return ashvardanian::stringzilla::_with_alloc<allocator_type_>(callback);
+    }
+
+    bool is_internal() const noexcept { return sz_string_is_on_stack(&string_); }
+
+    void init(std::size_t length, char_type value) noexcept(false) {
+        sz_ptr_t start;
+        if (!_with_alloc(
+                [&](sz_alloc_type &alloc) { return (start = sz_string_init_length(&string_, length, &alloc)); }))
+            throw std::bad_alloc();
+        sz_fill(start, length, *(sz_u8_t *)&value);
+    }
+
+    void init(string_view other) noexcept(false) {
+        sz_ptr_t start;
+        if (!_with_alloc(
+                [&](sz_alloc_type &alloc) { return (start = sz_string_init_length(&string_, other.size(), &alloc)); }))
+            throw std::bad_alloc();
+        sz_copy(start, (sz_cptr_t)other.data(), other.size());
+    }
+
+    void move(basic_string &other) noexcept {
+        // We can't just assign the other string state, as its start address may be somewhere else on the stack.
+        sz_ptr_t string_start;
+        sz_size_t string_length;
+        sz_size_t string_space;
+        sz_bool_t string_is_external;
+        sz_string_unpack(&other.string_, &string_start, &string_length, &string_space, &string_is_external);
+
+        // Acquire the old string's value bitwise
+        *(&string_) = *(&other.string_);
+        // Reposition the string start pointer to the stack if it fits.
+        // Ternary condition may be optimized to a branchless version.
+        string_.internal.start = string_is_external ? string_.internal.start : &string_.internal.chars[0];
+        sz_string_init(&other.string_); // Discard the other string.
+    }
+
+  public:
+    // STL compatibility
+    using traits_type = std::char_traits<char_type>;
+    using value_type = char_type;
+    using pointer = char_type *;
+    using const_pointer = char_type const *;
+    using reference = char_type &;
+    using const_reference = char_type const &;
+    using const_iterator = const_pointer;
+    using iterator = pointer;
+    using const_reverse_iterator = reversed_iterator_for<char_type const>;
+    using reverse_iterator = reversed_iterator_for<char_type>;
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+
+    // Non-STL type definitions
+    using allocator_type = allocator_type_;
+    using string_span = basic_string_slice<char_type>;
+    using string_view = basic_string_slice<typename std::add_const<char_type>::type>;
+    using partition_type = string_partition_result<string_view>;
+
+    /** @brief  Special value for missing matches.
+     *          We take the largest 63-bit unsigned integer.
+     */
+    static constexpr size_type npos = 0x7FFFFFFFFFFFFFFFull;
+
+#pragma region Constructors and STL Utilities
+
+    sz_constexpr_if_cpp20 basic_string() noexcept {
+        // ! Instead of relying on the `sz_string_init`, we have to reimplement it to support `constexpr`.
+        string_.internal.start = &string_.internal.chars[0];
+        string_.u64s[1] = 0;
+        string_.u64s[2] = 0;
+        string_.u64s[3] = 0;
+    }
+
+    ~basic_string() noexcept {
+        _with_alloc([&](sz_alloc_type &alloc) {
+            sz_string_free(&string_, &alloc);
+            return true;
+        });
+    }
+
+    basic_string(basic_string &&other) noexcept { move(other); }
+    basic_string &operator=(basic_string &&other) noexcept {
+        if (!is_internal()) {
+            _with_alloc([&](sz_alloc_type &alloc) {
+                sz_string_free(&string_, &alloc);
+                return true;
+            });
+        }
+        move(other);
+        return *this;
+    }
+
+    basic_string(basic_string const &other) noexcept(false) { init(other); }
+    basic_string &operator=(basic_string const &other) noexcept(false) { return assign(other); }
+    basic_string(string_view view) noexcept(false) { init(view); }
+    basic_string &operator=(string_view view) noexcept(false) { return assign(view); }
+
+    basic_string(const_pointer c_string) noexcept(false) : basic_string(string_view(c_string)) {}
+    basic_string(const_pointer c_string, size_type length) noexcept(false)
+        : basic_string(string_view(c_string, length)) {}
+    basic_string &operator=(const_pointer other) noexcept(false) { return assign(string_view(other)); }
+
+    basic_string(std::nullptr_t) = delete;
+
+    /**  @brief  Construct a string by repeating a certain ::character ::count times. */
+    basic_string(size_type count, value_type character) noexcept(false) { init(count, character); }
+
+    basic_string(basic_string const &other, size_type pos) noexcept(false) { init(string_view(other).substr(pos)); }
+    basic_string(basic_string const &other, size_type pos, size_type count) noexcept(false) {
+        init(string_view(other).substr(pos, count));
+    }
+
+    basic_string(std::initializer_list<value_type> list) noexcept(false) {
+        init(string_view(list.begin(), list.size()));
+    }
+
+    operator string_view() const noexcept { return view(); }
+    string_view view() const noexcept {
+        sz_ptr_t string_start;
+        sz_size_t string_length;
+        sz_string_range(&string_, &string_start, &string_length);
+        return {string_start, string_length};
+    }
+
+    operator string_span() noexcept { return span(); }
+    string_span span() noexcept {
+        sz_ptr_t string_start;
+        sz_size_t string_length;
+        sz_string_range(&string_, &string_start, &string_length);
+        return {string_start, string_length};
+    }
+
+    /**  @brief Exchanges the string contents witt the `other` string. */
+    void swap(basic_string &other) noexcept {
+        // If at least one of the strings is on the stack, a basic `std::swap(string_, other.string_)` won't work,
+        // as the pointer to the stack-allocated memory will be swapped, instead of the contents.
+        sz_ptr_t first_start, second_start;
+        sz_size_t first_length, second_length;
+        sz_size_t first_space, second_space;
+        sz_bool_t first_is_external, second_is_external;
+        sz_string_unpack(&string_, &first_start, &first_length, &first_space, &first_is_external);
+        sz_string_unpack(&other.string_, &second_start, &second_length, &second_space, &second_is_external);
+        std::swap(string_, other.string_);
+        if (!first_is_external) other.string_.internal.start = &other.string_.internal.chars[0];
+        if (!second_is_external) string_.internal.start = &string_.internal.chars[0];
+    }
+
+#if !SZ_AVOID_STL
+
+    basic_string(std::string const &other) noexcept(false) : basic_string(other.data(), other.size()) {}
+    basic_string &operator=(std::string const &other) noexcept(false) { return assign({other.data(), other.size()}); }
+
+    // As we are need both `data()` and `size()`, going through `operator string_view()`
+    // and `sz_string_unpack` is faster than separate invocations.
+    operator std::string() const { return view(); }
+
+    /**
+     *  @brief  Formatted output function for compatibility with STL's `std::basic_ostream`.
+     *  @throw  `std::ios_base::failure` if an exception occurred during output.
+     */
+    template <typename stream_traits>
+    friend std::basic_ostream<value_type, stream_traits> &operator<<(std::basic_ostream<value_type, stream_traits> &os,
+                                                                     basic_string const &str) noexcept(false) {
+        return os.write(str.data(), str.size());
+    }
+
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+
+    basic_string(std::string_view other) noexcept(false) : basic_string(other.data(), other.size()) {}
+    basic_string &operator=(std::string_view other) noexcept(false) { return assign({other.data(), other.size()}); }
+    operator std::string_view() const noexcept { return view(); }
+
+#endif
+
+#endif
+
+    template <typename first_type, typename second_type>
+    explicit basic_string(concatenation<first_type, second_type> const &expression) noexcept(false) {
+        _with_alloc([&](sz_alloc_type &alloc) {
+            sz_ptr_t ptr = sz_string_init_length(&string_, expression.length(), &alloc);
+            if (!ptr) return false;
+            expression.copy(ptr);
+            return true;
+        });
+    }
+
+    template <typename first_type, typename second_type>
+    basic_string &operator=(concatenation<first_type, second_type> const &expression) noexcept(false) {
+        if (!try_assign(expression)) throw std::bad_alloc();
+        return *this;
+    }
+
+#pragma endregion
+
+#pragma region Iterators and Accessors
+
+    iterator begin() noexcept { return iterator(data()); }
+    const_iterator begin() const noexcept { return const_iterator(data()); }
+    const_iterator cbegin() const noexcept { return const_iterator(data()); }
+
+    // As we are need both `data()` and `size()`, going through `operator string_view()`
+    // and `sz_string_unpack` is faster than separate invocations.
+    iterator end() noexcept { return span().end(); }
+    const_iterator end() const noexcept { return view().end(); }
+    const_iterator cend() const noexcept { return view().end(); }
+
+    reverse_iterator rbegin() noexcept { return span().rbegin(); }
+    const_reverse_iterator rbegin() const noexcept { return view().rbegin(); }
+    const_reverse_iterator crbegin() const noexcept { return view().crbegin(); }
+
+    reverse_iterator rend() noexcept { return span().rend(); }
+    const_reverse_iterator rend() const noexcept { return view().rend(); }
+    const_reverse_iterator crend() const noexcept { return view().crend(); }
+
+    reference operator[](size_type pos) noexcept { return string_.internal.start[pos]; }
+    const_reference operator[](size_type pos) const noexcept { return string_.internal.start[pos]; }
+
+    reference front() noexcept { return string_.internal.start[0]; }
+    const_reference front() const noexcept { return string_.internal.start[0]; }
+    reference back() noexcept { return string_.internal.start[size() - 1]; }
+    const_reference back() const noexcept { return string_.internal.start[size() - 1]; }
+    pointer data() noexcept { return string_.internal.start; }
+    const_pointer data() const noexcept { return string_.internal.start; }
+    pointer c_str() noexcept { return string_.internal.start; }
+    const_pointer c_str() const noexcept { return string_.internal.start; }
+
+    reference at(size_type pos) noexcept(false) {
+        if (pos >= size()) throw std::out_of_range("sz::basic_string::at");
+        return string_.internal.start[pos];
+    }
+    const_reference at(size_type pos) const noexcept(false) {
+        if (pos >= size()) throw std::out_of_range("sz::basic_string::at");
+        return string_.internal.start[pos];
+    }
+
+    difference_type ssize() const noexcept { return static_cast<difference_type>(size()); }
+    size_type size() const noexcept { return view().size(); }
+    size_type length() const noexcept { return size(); }
+    size_type max_size() const noexcept { return npos - 1; }
+    bool empty() const noexcept { return string_.external.length == 0; }
+    size_type capacity() const noexcept {
+        sz_ptr_t string_start;
+        sz_size_t string_length;
+        sz_size_t string_space;
+        sz_bool_t string_is_external;
+        sz_string_unpack(&string_, &string_start, &string_length, &string_space, &string_is_external);
+        return string_space - 1;
+    }
+
+    allocator_type get_allocator() const noexcept { return {}; }
+
+#pragma endregion
+
+#pragma region Slicing
+
+#pragma region Safe and Signed Extensions
+
+    /**
+     *  @brief  Equivalent to Python's `"abc"[-3:-1]`. Exception-safe, unlike STL's `substr`.
+     *          Supports signed and unsigned intervals.
+     */
+    string_view operator[](std::initializer_list<difference_type> offsets) const noexcept { return view()[offsets]; }
+    string_span operator[](std::initializer_list<difference_type> offsets) noexcept { return span()[offsets]; }
+
+    /**
+     *  @brief  Signed alternative to `at()`. Handy if you often write `str[str.size() - 2]`.
+     *  @warning The behavior is @b undefined if the position is beyond bounds.
+     */
+    value_type sat(difference_type offset) const noexcept { return view().sat(offset); }
+    reference sat(difference_type offset) noexcept { return span().sat(offset); }
+
+    /**
+     *  @brief  The opposite operation to `remove_prefix`, that does no bounds checking.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    string_view front(size_type n) const noexcept { return view().front(n); }
+    string_span front(size_type n) noexcept { return span().front(n); }
+
+    /**
+     *  @brief  The opposite operation to `remove_prefix`, that does no bounds checking.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    string_view back(size_type n) const noexcept { return view().back(n); }
+    string_span back(size_type n) noexcept { return span().back(n); }
+
+    /**
+     *  @brief  Equivalent to Python's `"abc"[-3:-1]`. Exception-safe, unlike STL's `substr`.
+     *          Supports signed and unsigned intervals. @b Doesn't copy or allocate memory!
+     */
+    string_view sub(difference_type start, difference_type end = npos) const noexcept { return view().sub(start, end); }
+    string_span sub(difference_type start, difference_type end = npos) noexcept { return span().sub(start, end); }
+
+    /**
+     *  @brief  Exports this entire view. Not an STL function, but useful for concatenations.
+     *          The STL variant expects at least two arguments.
+     */
+    size_type copy(value_type *destination) const noexcept { return view().copy(destination); }
+
+#pragma endregion
+
+#pragma region STL Style
+
+    /**
+     *  @brief  Removes the first `n` characters from the view.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    void remove_prefix(size_type n) noexcept {
+        assert(n <= size());
+        sz_string_erase(&string_, 0, n);
+    }
+
+    /**
+     *  @brief  Removes the last `n` characters from the view.
+     *  @warning The behavior is @b undefined if `n > size()`.
+     */
+    void remove_suffix(size_type n) noexcept {
+        assert(n <= size());
+        sz_string_erase(&string_, size() - n, n);
+    }
+
+    /**  @brief  Added for STL compatibility. */
+    basic_string substr() const noexcept { return *this; }
+
+    /**
+     *  @brief  Return a slice of this view after first `skip` bytes.
+     *  @throws `std::out_of_range` if `skip > size()`.
+     *  @see    `sub` for a cleaner exception-less alternative.
+     */
+    basic_string substr(size_type skip) const noexcept(false) { return view().substr(skip); }
+
+    /**
+     *  @brief  Return a slice of this view after first `skip` bytes, taking at most `count` bytes.
+     *  @throws `std::out_of_range` if `skip > size()`.
+     *  @see    `sub` for a cleaner exception-less alternative.
+     */
+    basic_string substr(size_type skip, size_type count) const noexcept(false) { return view().substr(skip, count); }
+
+    /**
+     *  @brief  Exports a slice of this view after first `skip` bytes, taking at most `count` bytes.
+     *  @throws `std::out_of_range` if `skip > size()`.
+     *  @see    `sub` for a cleaner exception-less alternative.
+     */
+    size_type copy(value_type *destination, size_type count, size_type skip = 0) const noexcept(false) {
+        return view().copy(destination, count, skip);
+    }
+
+#pragma endregion
+
+#pragma endregion
+
+#pragma region Comparisons
+
+#pragma region Whole String Comparisons
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     */
+    int compare(string_view other) const noexcept { return view().compare(other); }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to `substr(pos1, count1).compare(other)`.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()`.
+     */
+    int compare(size_type pos1, size_type count1, string_view other) const noexcept(false) {
+        return view().compare(pos1, count1, other);
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to `substr(pos1, count1).compare(other.substr(pos2, count2))`.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()` or if `pos2 > other.size()`.
+     */
+    int compare(size_type pos1, size_type count1, string_view other, size_type pos2, size_type count2) const
+        noexcept(false) {
+        return view().compare(pos1, count1, other, pos2, count2);
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     */
+    int compare(const_pointer other) const noexcept { return view().compare(other); }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to substr(pos1, count1).compare(other).
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()`.
+     */
+    int compare(size_type pos1, size_type count1, const_pointer other) const noexcept(false) {
+        return view().compare(pos1, count1, other);
+    }
+
+    /**
+     *  @brief  Compares two strings lexicographically. If prefix matches, lengths are compared.
+     *          Equivalent to `substr(pos1, count1).compare({s, count2})`.
+     *  @return 0 if equal, negative if `*this` is less than `other`, positive if `*this` is greater than `other`.
+     *  @throw  `std::out_of_range` if `pos1 > size()`.
+     */
+    int compare(size_type pos1, size_type count1, const_pointer other, size_type count2) const noexcept(false) {
+        return view().compare(pos1, count1, other, count2);
+    }
+
+    /**  @brief  Checks if the string is equal to the other string. */
+    bool operator==(basic_string const &other) const noexcept { return view() == other.view(); }
+    bool operator==(string_view other) const noexcept { return view() == other; }
+    bool operator==(const_pointer other) const noexcept { return view() == string_view(other); }
+
+#if SZ_DETECT_CPP20
+
+    /**  @brief  Computes the lexicographic ordering between this and the ::other string. */
+    std::strong_ordering operator<=>(basic_string const &other) const noexcept { return view() <=> other.view(); }
+    std::strong_ordering operator<=>(string_view other) const noexcept { return view() <=> other; }
+    std::strong_ordering operator<=>(const_pointer other) const noexcept { return view() <=> string_view(other); }
+
+#else
+
+    /**  @brief  Checks if the string is not equal to the other string. */
+    bool operator!=(string_view other) const noexcept { return !operator==(other); }
+
+    /**  @brief  Checks if the string is lexicographically smaller than the other string. */
+    bool operator<(string_view other) const noexcept { return compare(other) == sz_less_k; }
+
+    /**  @brief  Checks if the string is lexicographically equal or smaller than the other string. */
+    bool operator<=(string_view other) const noexcept { return compare(other) != sz_greater_k; }
+
+    /**  @brief  Checks if the string is lexicographically greater than the other string. */
+    bool operator>(string_view other) const noexcept { return compare(other) == sz_greater_k; }
+
+    /**  @brief  Checks if the string is lexicographically equal or greater than the other string. */
+    bool operator>=(string_view other) const noexcept { return compare(other) != sz_less_k; }
+
+#endif
+
+#pragma endregion
+#pragma region Prefix and Suffix Comparisons
+
+    /**  @brief  Checks if the string starts with the other string. */
+    bool starts_with(string_view other) const noexcept { return view().starts_with(other); }
+
+    /**  @brief  Checks if the string starts with the other string. */
+    bool starts_with(const_pointer other) const noexcept { return view().starts_with(other); }
+
+    /**  @brief  Checks if the string starts with the other character. */
+    bool starts_with(value_type other) const noexcept { return view().starts_with(other); }
+
+    /**  @brief  Checks if the string ends with the other string. */
+    bool ends_with(string_view other) const noexcept { return view().ends_with(other); }
+
+    /**  @brief  Checks if the string ends with the other string. */
+    bool ends_with(const_pointer other) const noexcept { return view().ends_with(other); }
+
+    /**  @brief  Checks if the string ends with the other character. */
+    bool ends_with(value_type other) const noexcept { return view().ends_with(other); }
+
+#pragma endregion
+#pragma endregion
+
+#pragma region Matching Substrings
+
+    bool contains(string_view other) const noexcept { return view().contains(other); }
+    bool contains(value_type character) const noexcept { return view().contains(character); }
+    bool contains(const_pointer other) const noexcept { return view().contains(other); }
+
+#pragma region Returning offsets
+
+    /**
+     *  @brief  Find the first occurrence of a substring, skipping the first `skip` characters.
+     *          The behavior is @b undefined if `skip > size()`.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type find(string_view other, size_type skip = 0) const noexcept { return view().find(other, skip); }
+
+    /**
+     *  @brief  Find the first occurrence of a character, skipping the first `skip` characters.
+     *          The behavior is @b undefined if `skip > size()`.
+     *  @return The offset of the match, or `npos` if not found.
+     */
+    size_type find(value_type character, size_type skip = 0) const noexcept { return view().find(character, skip); }
+
+    /**
+     *  @brief  Find the first occurrence of a substring, skipping the first `skip` characters.
+     *          The behavior is @b undefined if `skip > size()`.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type find(const_pointer other, size_type pos, size_type count) const noexcept {
+        return view().find(other, pos, count);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a substring.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type rfind(string_view other) const noexcept { return view().rfind(other); }
+
+    /**
+     *  @brief  Find the last occurrence of a substring, within first `until` characters.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type rfind(string_view other, size_type until) const noexcept { return view().rfind(other, until); }
+
+    /**
+     *  @brief  Find the last occurrence of a character.
+     *  @return The offset of the match, or `npos` if not found.
+     */
+    size_type rfind(value_type character) const noexcept { return view().rfind(character); }
+
+    /**
+     *  @brief  Find the last occurrence of a character, within first `until` characters.
+     *  @return The offset of the match, or `npos` if not found.
+     */
+    size_type rfind(value_type character, size_type until) const noexcept { return view().rfind(character, until); }
+
+    /**
+     *  @brief  Find the last occurrence of a substring, within first `until` characters.
+     *  @return The offset of the first character of the match, or `npos` if not found.
+     */
+    size_type rfind(const_pointer other, size_type until, size_type count) const noexcept {
+        return view().rfind(other, until, count);
+    }
+
+    /**  @brief  Find the first occurrence of a character from a set. */
+    size_type find(char_set set) const noexcept { return view().find(set); }
+
+    /**  @brief  Find the last occurrence of a character from a set. */
+    size_type rfind(char_set set) const noexcept { return view().rfind(set); }
+
+#pragma endregion
+#pragma endregion
+
+#pragma region Matching Character Sets
+
+    bool contains_only(char_set set) const noexcept { return find_first_not_of(set) == npos; }
+    bool is_alpha() const noexcept { return !empty() && contains_only(ascii_letters_set()); }
+    bool is_alnum() const noexcept { return !empty() && contains_only(ascii_letters_set() | digits_set()); }
+    bool is_ascii() const noexcept { return empty() || contains_only(ascii_controls_set() | ascii_printables_set()); }
+    bool is_digit() const noexcept { return !empty() && contains_only(digits_set()); }
+    bool is_lower() const noexcept { return !empty() && contains_only(ascii_lowercase_set()); }
+    bool is_space() const noexcept { return !empty() && contains_only(whitespaces_set()); }
+    bool is_upper() const noexcept { return !empty() && contains_only(ascii_uppercase_set()); }
+    bool is_printable() const noexcept { return empty() || contains_only(ascii_printables_set()); }
+
+#pragma region Character Set Arguments
+
+    /**
+     *  @brief  Find the first occurrence of a character from a set.
+     *  @param  skip Number of characters to skip before the search.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_of(char_set set, size_type skip = 0) const noexcept { return view().find_first_of(set, skip); }
+
+    /**
+     *  @brief  Find the first occurrence of a character outside a set.
+     *  @param  skip  The number of first characters to be skipped.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_not_of(char_set set, size_type skip = 0) const noexcept {
+        return view().find_first_not_of(set, skip);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a set.
+     */
+    size_type find_last_of(char_set set) const noexcept { return view().find_last_of(set); }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a set.
+     */
+    size_type find_last_not_of(char_set set) const noexcept { return view().find_last_not_of(set); }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_of(char_set set, size_type until) const noexcept { return view().find_last_of(set, until); }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_not_of(char_set set, size_type until) const noexcept {
+        return view().find_last_not_of(set, until);
+    }
+
+#pragma endregion
+#pragma region String Arguments
+
+    /**
+     *  @brief  Find the first occurrence of a character from a ::set.
+     *  @param  skip  The number of first characters to be skipped.
+     */
+    size_type find_first_of(string_view other, size_type skip = 0) const noexcept {
+        return view().find_first_of(other, skip);
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a character outside a ::set.
+     *  @param  skip  The number of first characters to be skipped.
+     */
+    size_type find_first_not_of(string_view other, size_type skip = 0) const noexcept {
+        return view().find_first_not_of(other, skip);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a ::set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_of(string_view other, size_type until = npos) const noexcept {
+        return view().find_last_of(other, until);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a ::set.
+     *  @param  until  The offset of the last character to be considered.
+     */
+    size_type find_last_not_of(string_view other, size_type until = npos) const noexcept {
+        return view().find_last_not_of(other, until);
+    }
+
+#pragma endregion
+#pragma region C-Style Arguments
+
+    /**
+     *  @brief  Find the first occurrence of a character from a set.
+     *  @param  skip  The number of first characters to be skipped.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_of(const_pointer other, size_type skip, size_type count) const noexcept {
+        return view().find_first_of(other, skip, count);
+    }
+
+    /**
+     *  @brief  Find the first occurrence of a character outside a set.
+     *  @param  skip  The number of first characters to be skipped.
+     *  @warning The behavior is @b undefined if `skip > size()`.
+     */
+    size_type find_first_not_of(const_pointer other, size_type skip, size_type count) const noexcept {
+        return view().find_first_not_of(other, skip, count);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character from a set.
+     *  @param  until  The number of first characters to be considered.
+     */
+    size_type find_last_of(const_pointer other, size_type until, size_type count) const noexcept {
+        return view().find_last_of(other, until, count);
+    }
+
+    /**
+     *  @brief  Find the last occurrence of a character outside a set.
+     *  @param  until  The number of first characters to be considered.
+     */
+    size_type find_last_not_of(const_pointer other, size_type until, size_type count) const noexcept {
+        return view().find_last_not_of(other, until, count);
+    }
+
+#pragma endregion
+#pragma region Slicing
+
+    /**
+     *  @brief  Python-like convenience function, dropping prefix formed of given characters.
+     *          Similar to `boost::algorithm::trim_left_if(str, is_any_of(set))`.
+     */
+    basic_string &lstrip(char_set set) noexcept {
+        auto remaining = view().lstrip(set);
+        remove_prefix(size() - remaining.size());
+        return *this;
+    }
+
+    /**
+     *  @brief  Python-like convenience function, dropping suffix formed of given characters.
+     *          Similar to `boost::algorithm::trim_right_if(str, is_any_of(set))`.
+     */
+    basic_string &rstrip(char_set set) noexcept {
+        auto remaining = view().rstrip(set);
+        remove_suffix(size() - remaining.size());
+        return *this;
+    }
+
+    /**
+     *  @brief  Python-like convenience function, dropping both the prefix & the suffix formed of given characters.
+     *          Similar to `boost::algorithm::trim_if(str, is_any_of(set))`.
+     */
+    basic_string &strip(char_set set) noexcept { return lstrip(set).rstrip(set); }
+
+#pragma endregion
+#pragma endregion
+
+#pragma region Modifiers
+#pragma region Non-STL API
+
+    bool try_resize(size_type count, value_type character = '\0') noexcept;
+
+    bool try_reserve(size_type capacity) noexcept {
+        return _with_alloc([&](sz_alloc_type &alloc) { return sz_string_reserve(&string_, capacity, &alloc); });
+    }
+
+    bool try_assign(string_view other) noexcept;
+
+    template <typename first_type, typename second_type>
+    bool try_assign(concatenation<first_type, second_type> const &other) noexcept;
+
+    bool try_push_back(char_type c) noexcept;
+
+    bool try_append(const_pointer str, size_type length) noexcept;
+
+    bool try_append(string_view str) noexcept { return try_append(str.data(), str.size()); }
+
+    /**
+     *  @brief  Erases ( @b in-place ) a range of characters defined with signed offsets.
+     *  @return Number of characters removed.
+     */
+    size_type try_erase(difference_type signed_start_offset = 0, difference_type signed_end_offset = npos) noexcept {
+        sz_size_t normalized_offset, normalized_length;
+        sz_ssize_clamp_interval(size(), signed_start_offset, signed_end_offset, &normalized_offset, &normalized_length);
+        if (!normalized_length) return false;
+        sz_string_erase(&string_, normalized_offset, normalized_length);
+        return normalized_length;
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a range of characters at a given signed offset.
+     *  @return `true` if the insertion was successful, `false` otherwise.
+     */
+    bool try_insert(difference_type signed_offset, string_view string) noexcept {
+        sz_size_t normalized_offset, normalized_length;
+        sz_ssize_clamp_interval(size(), signed_offset, 0, &normalized_offset, &normalized_length);
+        if (!_with_alloc([&](sz_alloc_type &alloc) {
+                return sz_string_expand(&string_, normalized_offset, string.size(), &alloc);
+            }))
+            return false;
+
+        sz_copy(data() + normalized_offset, string.data(), string.size());
+        return true;
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @return `true` if the replacement was successful, `false` otherwise.
+     */
+    bool try_replace(difference_type signed_start_offset, difference_type signed_end_offset,
+                     string_view replacement) noexcept {
+
+        sz_size_t normalized_offset, normalized_length;
+        sz_ssize_clamp_interval(size(), signed_start_offset, signed_end_offset, &normalized_offset, &normalized_length);
+        if (!try_preparing_replacement(normalized_offset, normalized_length, replacement)) return false;
+        sz_copy(data() + normalized_offset, replacement.data(), replacement.size());
+        return true;
+    }
+
+#pragma endregion
+
+#pragma region STL Interfaces
+
+    /**
+     *  @brief  Clears the string contents, but @b no deallocations happen.
+     */
+    void clear() noexcept { sz_string_erase(&string_, 0, SZ_SIZE_MAX); }
+
+    /**
+     *  @brief  Resizes the string to the given size, filling the new space with the given character,
+     *          or NULL-character if nothing is provided.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    void resize(size_type count, value_type character = '\0') noexcept(false) {
+        if (count > max_size()) throw std::length_error("sz::basic_string::resize");
+        if (!try_resize(count, character)) throw std::bad_alloc();
+    }
+
+    /**
+     *  @brief  Informs the string object of a planned change in size, so that it pre-allocate once.
+     *  @throw  `std::length_error` if the string is too long.
+     */
+    void reserve(size_type capacity) noexcept(false) {
+        if (capacity > max_size()) throw std::length_error("sz::basic_string::reserve");
+        if (!try_reserve(capacity)) throw std::bad_alloc();
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a ::character multiple times at the given offset.
+     *  @throw  `std::out_of_range` if `offset > size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    basic_string &insert(size_type offset, size_type repeats, char_type character) noexcept(false) {
+        if (offset > size()) throw std::out_of_range("sz::basic_string::insert");
+        if (size() + repeats > max_size()) throw std::length_error("sz::basic_string::insert");
+        if (!_with_alloc([&](sz_alloc_type &alloc) { return sz_string_expand(&string_, offset, repeats, &alloc); }))
+            throw std::bad_alloc();
+
+        sz_fill(data() + offset, repeats, character);
+        return *this;
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a range of characters at the given offset.
+     *  @throw  `std::out_of_range` if `offset > size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    basic_string &insert(size_type offset, string_view other) noexcept(false) {
+        if (offset > size()) throw std::out_of_range("sz::basic_string::insert");
+        if (size() + other.size() > max_size()) throw std::length_error("sz::basic_string::insert");
+        if (!_with_alloc(
+                [&](sz_alloc_type &alloc) { return sz_string_expand(&string_, offset, other.size(), &alloc); }))
+            throw std::bad_alloc();
+
+        sz_copy(data() + offset, other.data(), other.size());
+        return *this;
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a range of characters at the given offset.
+     *  @throw  `std::out_of_range` if `offset > size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    basic_string &insert(size_type offset, const_pointer start, size_type length) noexcept(false) {
+        return insert(offset, string_view(start, length));
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a slice of another string at the given offset.
+     *  @throw  `std::out_of_range` if `offset > size()` or `other_index > other.size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    basic_string &insert(size_type offset, string_view other, size_type other_index,
+                         size_type count = npos) noexcept(false) {
+        return insert(offset, other.substr(other_index, count));
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) one ::character at the given iterator position.
+     *  @throw  `std::out_of_range` if `pos > size()` or `other_index > other.size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    iterator insert(const_iterator it, char_type character) noexcept(false) {
+        auto pos = range_length(cbegin(), it);
+        insert(pos, string_view(&character, 1));
+        return begin() + pos;
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a ::character multiple times at the given iterator position.
+     *  @throw  `std::out_of_range` if `pos > size()` or `other_index > other.size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    iterator insert(const_iterator it, size_type repeats, char_type character) noexcept(false) {
+        auto pos = range_length(cbegin(), it);
+        insert(pos, repeats, character);
+        return begin() + pos;
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) a range at the given iterator position.
+     *  @throw  `std::out_of_range` if `pos > size()` or `other_index > other.size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    template <typename input_iterator>
+    iterator insert(const_iterator it, input_iterator first, input_iterator last) noexcept(false) {
+
+        auto pos = range_length(cbegin(), it);
+        if (pos > size()) throw std::out_of_range("sz::basic_string::insert");
+
+        auto added_length = range_length(first, last);
+        if (size() + added_length > max_size()) throw std::length_error("sz::basic_string::insert");
+
+        if (!_with_alloc([&](sz_alloc_type &alloc) { return sz_string_expand(&string_, pos, added_length, &alloc); }))
+            throw std::bad_alloc();
+
+        iterator result = begin() + pos;
+        for (iterator output = result; first != last; ++first, ++output) *output = *first;
+        return result;
+    }
+
+    /**
+     *  @brief  Inserts ( @b in-place ) an initializer list of characters.
+     *  @throw  `std::out_of_range` if `pos > size()` or `other_index > other.size()`.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    iterator insert(const_iterator it, std::initializer_list<char_type> ilist) noexcept(false) {
+        return insert(it, ilist.begin(), ilist.end());
+    }
+
+    /**
+     *  @brief  Erases ( @b in-place ) the given range of characters.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @see    `try_erase_slice` for a cleaner exception-less alternative.
+     */
+    basic_string &erase(size_type pos = 0, size_type count = npos) noexcept(false) {
+        if (!count || empty()) return *this;
+        if (pos >= size()) throw std::out_of_range("sz::basic_string::erase");
+        sz_string_erase(&string_, pos, count);
+        return *this;
+    }
+
+    /**
+     *  @brief  Erases ( @b in-place ) the given range of characters.
+     *  @return Iterator pointing following the erased character, or end() if no such character exists.
+     */
+    iterator erase(const_iterator first, const_iterator last) noexcept {
+        auto start = begin();
+        auto offset = first - start;
+        sz_string_erase(&string_, offset, last - first);
+        return start + offset;
+    }
+
+    /**
+     *  @brief  Erases ( @b in-place ) the one character at a given postion.
+     *  @return Iterator pointing following the erased character, or end() if no such character exists.
+     */
+    iterator erase(const_iterator pos) noexcept { return erase(pos, pos + 1); }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(size_type pos, size_type count, string_view const &str) noexcept(false) {
+        if (pos > size()) throw std::out_of_range("sz::basic_string::replace");
+        if (size() - count + str.size() > max_size()) throw std::length_error("sz::basic_string::replace");
+        if (!try_preparing_replacement(pos, count, str.size())) throw std::bad_alloc();
+        sz_copy(data() + pos, str.data(), str.size());
+        return *this;
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(const_iterator first, const_iterator last, string_view const &str) noexcept(false) {
+        return replace(range_length(cbegin(), first), last - first, str);
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()` or `pos2 > str.size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(size_type pos, size_type count, string_view const &str, size_type pos2,
+                          size_type count2 = npos) noexcept(false) {
+        return replace(pos, count, str.substr(pos2, count2));
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(size_type pos, size_type count, const_pointer cstr, size_type count2) noexcept(false) {
+        return replace(pos, count, string_view(cstr, count2));
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(const_iterator first, const_iterator last, const_pointer cstr,
+                          size_type count2) noexcept(false) {
+        return replace(range_length(cbegin(), first), last - first, string_view(cstr, count2));
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(size_type pos, size_type count, const_pointer cstr) noexcept(false) {
+        return replace(pos, count, string_view(cstr));
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(const_iterator first, const_iterator last, const_pointer cstr) noexcept(false) {
+        return replace(range_length(cbegin(), first), last - first, string_view(cstr));
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a repetition of given characters.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(size_type pos, size_type count, size_type count2, char_type character) noexcept(false) {
+        if (pos > size()) throw std::out_of_range("sz::basic_string::replace");
+        if (size() - count + count2 > max_size()) throw std::length_error("sz::basic_string::replace");
+        if (!try_preparing_replacement(pos, count, count2)) throw std::bad_alloc();
+        sz_fill(data() + pos, count2, character);
+        return *this;
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a repetition of given characters.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(const_iterator first, const_iterator last, size_type count2,
+                          char_type character) noexcept(false) {
+        return replace(range_length(cbegin(), first), last - first, count2, character);
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given string.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    template <typename input_iterator>
+    basic_string &replace(const_iterator first, const_iterator last, input_iterator first2,
+                          input_iterator last2) noexcept(false) {
+        auto pos = range_length(cbegin(), first);
+        auto count = range_length(first, last);
+        auto count2 = range_length(first2, last2);
+        if (pos > size()) throw std::out_of_range("sz::basic_string::replace");
+        if (size() - count + count2 > max_size()) throw std::length_error("sz::basic_string::replace");
+        if (!try_preparing_replacement(pos, count, count2)) throw std::bad_alloc();
+        for (iterator output = begin() + pos; first2 != last2; ++first2, ++output) *output = *first2;
+        return *this;
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) a range of characters with a given initializer list.
+     *  @throws `std::out_of_range` if `pos > size()`.
+     *  @throws `std::length_error` if the string is too long.
+     *  @see    `try_replace` for a cleaner exception-less alternative.
+     */
+    basic_string &replace(const_iterator first, const_iterator last,
+                          std::initializer_list<char_type> ilist) noexcept(false) {
+        return replace(first, last, ilist.begin(), ilist.end());
+    }
+
+    /**
+     *  @brief  Appends the given character at the end.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     */
+    void push_back(char_type ch) noexcept(false) {
+        if (size() == max_size()) throw std::length_error("string::push_back");
+        if (!try_push_back(ch)) throw std::bad_alloc();
+    }
+
+    /**
+     *  @brief  Removes the last character from the string.
+     *  @warning The behavior is @b undefined if the string is empty.
+     */
+    void pop_back() noexcept { sz_string_erase(&string_, size() - 1, 1); }
+
+    /**
+     *  @brief  Overwrites the string with the given string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_assign` for a cleaner exception-less alternative.
+     */
+    basic_string &assign(string_view other) noexcept(false) {
+        if (!try_assign(other)) throw std::bad_alloc();
+        return *this;
+    }
+
+    /**
+     *  @brief  Overwrites the string with the given repeated character.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_assign` for a cleaner exception-less alternative.
+     */
+    basic_string &assign(size_type repeats, char_type character) noexcept(false) {
+        resize(repeats, character);
+        sz_fill(data(), repeats, *(sz_u8_t *)&character);
+        return *this;
+    }
+
+    /**
+     *  @brief  Overwrites the string with the given string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_assign` for a cleaner exception-less alternative.
+     */
+    basic_string &assign(const_pointer other, size_type length) noexcept(false) { return assign({other, length}); }
+
+    /**
+     *  @brief  Overwrites the string with the given string.
+     *  @throw  `std::length_error` if the string is too long or `pos > str.size()`.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_assign` for a cleaner exception-less alternative.
+     */
+    basic_string &assign(string_view str, size_type pos, size_type count = npos) noexcept(false) {
+        return assign(str.substr(pos, count));
+    }
+
+    /**
+     *  @brief  Overwrites the string with the given iterator range.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_assign` for a cleaner exception-less alternative.
+     */
+    template <typename input_iterator>
+    basic_string &assign(input_iterator first, input_iterator last) noexcept(false) {
+        resize(range_length(first, last));
+        for (iterator output = begin(); first != last; ++first, ++output) *output = *first;
+        return *this;
+    }
+
+    /**
+     *  @brief  Overwrites the string with the given initializer list.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_assign` for a cleaner exception-less alternative.
+     */
+    basic_string &assign(std::initializer_list<char_type> ilist) noexcept(false) {
+        return assign(ilist.begin(), ilist.end());
+    }
+
+    /**
+     *  @brief  Appends to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    basic_string &append(string_view str) noexcept(false) {
+        if (!try_append(str)) throw std::bad_alloc();
+        return *this;
+    }
+
+    /**
+     *  @brief  Appends to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long or `pos > str.size()`.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    basic_string &append(string_view str, size_type pos, size_type length = npos) noexcept(false) {
+        return append(str.substr(pos, length));
+    }
+
+    /**
+     *  @brief  Appends to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    basic_string &append(const_pointer str, size_type length) noexcept(false) { return append({str, length}); }
+
+    /**
+     *  @brief  Appends to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    basic_string &append(const_pointer str) noexcept(false) { return append(string_view(str)); }
+
+    /**
+     *  @brief  Appends a repeated character to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    basic_string &append(size_type repeats, char_type ch) noexcept(false) {
+        resize(size() + repeats, ch);
+        return *this;
+    }
+
+    /**
+     *  @brief  Appends to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    basic_string &append(std::initializer_list<char_type> other) noexcept(false) {
+        return append(other.begin(), other.end());
+    }
+
+    /**
+     *  @brief  Appends to the end of the current string.
+     *  @throw  `std::length_error` if the string is too long.
+     *  @throw  `std::bad_alloc` if the allocation fails.
+     *  @see    `try_append` for a cleaner exception-less alternative.
+     */
+    template <typename input_iterator>
+    basic_string &append(input_iterator first, input_iterator last) noexcept(false) {
+        insert<input_iterator>(cend(), first, last);
+        return *this;
+    }
+
+    basic_string &operator+=(string_view other) noexcept(false) { return append(other); }
+    basic_string &operator+=(std::initializer_list<char_type> other) noexcept(false) { return append(other); }
+    basic_string &operator+=(char_type character) noexcept(false) { return operator+=(string_view(&character, 1)); }
+    basic_string &operator+=(const_pointer other) noexcept(false) { return operator+=(string_view(other)); }
+
+    basic_string operator+(char_type character) const noexcept(false) { return operator+(string_view(&character, 1)); }
+    basic_string operator+(const_pointer other) const noexcept(false) { return operator+(string_view(other)); }
+    basic_string operator+(string_view other) const noexcept(false) {
+        return basic_string {concatenation<string_view, string_view> {view(), other}};
+    }
+    basic_string operator+(std::initializer_list<char_type> other) const noexcept(false) {
+        return basic_string {concatenation<string_view, string_view> {view(), other}};
+    }
+
+#pragma endregion
+#pragma endregion
+
+    concatenation<string_view, string_view> operator|(string_view other) const noexcept { return {view(), other}; }
+
+    size_type edit_distance(string_view other, size_type bound = 0) const noexcept {
+        size_type distance;
+        _with_alloc([&](sz_alloc_type &alloc) {
+            distance = sz_edit_distance(data(), size(), other.data(), other.size(), bound, &alloc);
+            return true;
+        });
+        return distance;
+    }
+
+    /**  @brief  Hashes the string, equivalent to `std::hash<string_view>{}(str)`. */
+    size_type hash() const noexcept { return view().hash(); }
+
+    /**
+     *  @brief  Overwrites the string with random characters from the given alphabet using the random generator.
+     *
+     *  @param  generator  A random generator function object that returns a random number in the range [0, 2^64).
+     *  @param  alphabet   A string of characters to choose from.
+     */
+    template <typename generator_type>
+    basic_string &randomize(generator_type &generator, string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept {
+        sz_ptr_t start;
+        sz_size_t length;
+        sz_string_range(&string_, &start, &length);
+        sz_random_generator_t generator_callback = &_call_random_generator<generator_type>;
+        sz_generate(alphabet.data(), alphabet.size(), start, length, generator_callback, &generator);
+        return *this;
+    }
+
+    /**
+     *  @brief  Overwrites the string with random characters from the given alphabet
+     *          using `std::rand` as the random generator.
+     *
+     *  @param  alphabet   A string of characters to choose from.
+     */
+    basic_string &randomize(string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept {
+        return randomize(&std::rand, alphabet);
+    }
+
+    /**
+     *  @brief  Generate a new random string of given length using `std::rand` as the random generator.
+     *          May throw exceptions if the memory allocation fails.
+     *
+     *  @param  length     The length of the generated string.
+     *  @param  alphabet   A string of characters to choose from.
+     */
+    static basic_string random(size_type length, string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept(false) {
+        return basic_string(length, '\0').randomize(alphabet);
+    }
+
+    /**
+     *  @brief  Generate a new random string of given length using the provided random number generator.
+     *          May throw exceptions if the memory allocation fails.
+     *
+     *  @param  generator  A random generator function object that returns a random number in the range [0, 2^64).
+     *  @param  length     The length of the generated string.
+     *  @param  alphabet   A string of characters to choose from.
+     */
+    template <typename generator_type>
+    static basic_string random(generator_type &generator, size_type length,
+                               string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept(false) {
+        return basic_string(length, '\0').randomize(generator, alphabet);
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) all occurrences of a given string with the ::replacement string.
+     *          Similar to `boost::algorithm::replace_all` and Python's `str.replace`.
+     *
+     *  The implementation is not as composable, as using search ranges combined with a replacing mapping for matches,
+     *  and might be suboptimal, if you are exporting the cleaned-up string to another buffer.
+     *  The algorithm is suboptimal when this string is made exclusively of the pattern.
+     */
+    basic_string &replace_all(string_view pattern, string_view replacement) noexcept(false) {
+        if (!try_replace_all(pattern, replacement)) throw std::bad_alloc();
+        return *this;
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) all occurrences of a given character set with the ::replacement string.
+     *          Similar to `boost::algorithm::replace_all` and Python's `str.replace`.
+     *
+     *  The implementation is not as composable, as using search ranges combined with a replacing mapping for matches,
+     *  and might be suboptimal, if you are exporting the cleaned-up string to another buffer.
+     *  The algorithm is suboptimal when this string is made exclusively of the pattern.
+     */
+    basic_string &replace_all(char_set pattern, string_view replacement) noexcept(false) {
+        if (!try_replace_all(pattern, replacement)) throw std::bad_alloc();
+        return *this;
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) all occurrences of a given string with the ::replacement string.
+     *          Similar to `boost::algorithm::replace_all` and Python's `str.replace`.
+     *
+     *  The implementation is not as composable, as using search ranges combined with a replacing mapping for matches,
+     *  and might be suboptimal, if you are exporting the cleaned-up string to another buffer.
+     *  The algorithm is suboptimal when this string is made exclusively of the pattern.
+     */
+    bool try_replace_all(string_view pattern, string_view replacement) noexcept {
+        return try_replace_all_<string_view>(pattern, replacement);
+    }
+
+    /**
+     *  @brief  Replaces ( @b in-place ) all occurrences of a given character set with the ::replacement string.
+     *          Similar to `boost::algorithm::replace_all` and Python's `str.replace`.
+     *
+     *  The implementation is not as composable, as using search ranges combined with a replacing mapping for matches,
+     *  and might be suboptimal, if you are exporting the cleaned-up string to another buffer.
+     *  The algorithm is suboptimal when this string is made exclusively of the pattern.
+     */
+    bool try_replace_all(char_set pattern, string_view replacement) noexcept {
+        return try_replace_all_<char_set>(pattern, replacement);
+    }
+
+  private:
+    template <typename pattern_type>
+    bool try_replace_all_(pattern_type pattern, string_view replacement) noexcept;
+
+    /**
+     *  @brief  Tries to prepare the string for a replacement of a given range with a new string.
+     *          The allocation may occur, if the replacement is longer than the replaced range.
+     */
+    bool try_preparing_replacement(size_type offset, size_type length, size_type new_length) noexcept;
+};
+
+using string = basic_string<char, std::allocator<char>>;
+
+static_assert(sizeof(string) == 4 * sizeof(void *), "String size must be 4 pointers.");
+
+namespace literals {
+constexpr string_view operator""_sz(char const *str, std::size_t length) noexcept { return {str, length}; }
+} // namespace literals
+
+template <typename char_type_, typename allocator_>
+bool basic_string<char_type_, allocator_>::try_resize(size_type count, value_type character) noexcept {
+    sz_ptr_t string_start;
+    sz_size_t string_length;
+    sz_size_t string_space;
+    sz_bool_t string_is_external;
+    sz_string_unpack(&string_, &string_start, &string_length, &string_space, &string_is_external);
+
+    // Allocate more space if needed.
+    if (count >= string_space) {
+        if (!_with_alloc(
+                [&](sz_alloc_type &alloc) { return sz_string_expand(&string_, SZ_SIZE_MAX, count, &alloc) != NULL; }))
+            return false;
+        sz_string_unpack(&string_, &string_start, &string_length, &string_space, &string_is_external);
+    }
+
+    // Fill the trailing characters.
+    if (count > string_length) {
+        sz_fill(string_start + string_length, count - string_length, character);
+        string_start[count] = '\0';
+        // Knowing the layout of the string, we can perform this operation safely,
+        // even if its located on stack.
+        string_.external.length += count - string_length;
+    }
+    else { sz_string_erase(&string_, count, SZ_SIZE_MAX); }
+    return true;
+}
+
+template <typename char_type_, typename allocator_>
+bool basic_string<char_type_, allocator_>::try_assign(string_view other) noexcept {
+    // We can't just assign the other string state, as its start address may be somewhere else on the stack.
+    sz_ptr_t string_start;
+    sz_size_t string_length;
+    sz_string_range(&string_, &string_start, &string_length);
+
+    if (string_length >= other.length()) {
+        other.copy(string_start, other.length());
+        sz_string_erase(&string_, other.length(), SZ_SIZE_MAX);
+    }
+    else {
+        if (!_with_alloc([&](sz_alloc_type &alloc) {
+                string_start = sz_string_expand(&string_, SZ_SIZE_MAX, other.length(), &alloc);
+                if (!string_start) return false;
+                other.copy(string_start, other.length());
+                return true;
+            }))
+            return false;
+    }
+    return true;
+}
+
+template <typename char_type_, typename allocator_>
+bool basic_string<char_type_, allocator_>::try_push_back(char_type c) noexcept {
+    return _with_alloc([&](sz_alloc_type &alloc) {
+        auto old_size = size();
+        sz_ptr_t start = sz_string_expand(&string_, SZ_SIZE_MAX, 1, &alloc);
+        if (!start) return false;
+        start[old_size] = c;
+        return true;
+    });
+}
+
+template <typename char_type_, typename allocator_>
+bool basic_string<char_type_, allocator_>::try_append(const_pointer str, size_type length) noexcept {
+    return _with_alloc([&](sz_alloc_type &alloc) {
+        auto old_size = size();
+        sz_ptr_t start = sz_string_expand(&string_, SZ_SIZE_MAX, length, &alloc);
+        if (!start) return false;
+        sz_copy(start + old_size, str, length);
+        return true;
+    });
+}
+
+template <typename char_type_, typename allocator_>
+template <typename pattern_type>
+bool basic_string<char_type_, allocator_>::try_replace_all_(pattern_type pattern, string_view replacement) noexcept {
+    // Depending on the size of the pattern and the replacement, we may need to allocate more space.
+    // There are 3 cases to consider:
+    // 1. The pattern and the replacement are of the same length. Piece of cake!
+    // 2. The pattern is longer than the replacement. We need to compact the strings.
+    // 3. The pattern is shorter than the replacement. We may have to allocate more memory.
+    using matcher_type = typename std::conditional<std::is_same<pattern_type, char_set>::value,
+                                                   matcher_find_first_of<string_view, pattern_type>,
+                                                   matcher_find<string_view, exclude_overlaps_type>>::type;
+    matcher_type matcher({pattern});
+    string_view this_view = view();
+
+    // 1. The pattern and the replacement are of the same length.
+    if (matcher.needle_length() == replacement.length()) {
+        using matches_type = range_matches<string_view, matcher_type>;
+        // Instead of iterating with `begin()` and `end()`, we could use the cheaper sentinel-based approach.
+        //      for (string_view match : matches) { ... }
+        matches_type matches = matches_type(this_view, {pattern});
+        for (auto matches_iterator = matches.begin(); matches_iterator != end_sentinel_type {}; ++matches_iterator) {
+            replacement.copy(const_cast<pointer>((*matches_iterator).data()));
+        }
+        return true;
+    }
+
+    // 2. The pattern is longer than the replacement. We need to compact the strings.
+    else if (matcher.needle_length() > replacement.length()) {
+        // Dealing with shorter replacements, we will avoid memory allocations, but we can also mimnimize the number
+        // of `memmove`-s, by keeping one more iterator, pointing to the end of the last compacted area.
+        // Having the split-ranges, however, we reuse their logic.
+        using splits_type = range_splits<string_view, matcher_type>;
+        splits_type splits = splits_type(this_view, {pattern});
+        auto matches_iterator = splits.begin();
+        auto compacted_end = (*matches_iterator).end();
+        if (compacted_end == end()) return true; // No matches.
+
+        ++matches_iterator; // Skip the first match.
+        do {
+            string_view match_view = *matches_iterator;
+            replacement.copy(const_cast<pointer>(compacted_end));
+            compacted_end += replacement.length();
+            sz_move((sz_ptr_t)compacted_end, match_view.begin(), match_view.length());
+            compacted_end += match_view.length();
+            ++matches_iterator;
+        } while (matches_iterator != end_sentinel_type {});
+
+        // Can't fail, so let's just return true :)
+        try_resize(compacted_end - begin());
+        return true;
+    }
+
+    // 3. The pattern is shorter than the replacement. We may have to allocate more memory.
+    else {
+        using rmatcher_type = typename std::conditional<std::is_same<pattern_type, char_set>::value,
+                                                        matcher_find_last_of<string_view, pattern_type>,
+                                                        matcher_rfind<string_view, exclude_overlaps_type>>::type;
+        using rmatches_type = range_rmatches<string_view, rmatcher_type>;
+        rmatches_type rmatches = rmatches_type(this_view, {pattern});
+
+        // It's cheaper to iterate through the whole string once, counting the number of matches,
+        // reserving memory once, than re-allocating and copying the string multiple times.
+        auto matches_count = rmatches.size();
+        if (matches_count == 0) return true; // No matches.
+
+        // TODO: Resize without initializing the memory.
+        auto replacement_delta_length = replacement.length() - matcher.needle_length();
+        auto added_length = matches_count * replacement_delta_length;
+        auto old_length = size();
+        auto new_length = old_length + added_length;
+        if (!try_resize(new_length)) return false;
+        this_view = view().front(old_length);
+
+        // Now iterate through splits similarly to the 2nd case, but in reverse order.
+        using rsplits_type = range_rsplits<string_view, rmatcher_type>;
+        rsplits_type splits = rsplits_type(this_view, {pattern});
+        auto splits_iterator = splits.begin();
+
+        // Put the compacted pointer to the end of the new string, and walk left.
+        auto compacted_begin = this_view.data() + new_length;
+
+        // By now we know that at least one match exists, which means the splits .
+        do {
+            string_view slice_view = *splits_iterator;
+            compacted_begin -= slice_view.length();
+            sz_move((sz_ptr_t)compacted_begin, slice_view.begin(), slice_view.length());
+            compacted_begin -= replacement.length();
+            replacement.copy(const_cast<pointer>(compacted_begin));
+            ++splits_iterator;
+        } while (!splits_iterator.is_last());
+
+        return true;
+    }
+}
+
+template <typename char_type_, typename allocator_>
+template <typename first_type, typename second_type>
+bool basic_string<char_type_, allocator_>::try_assign(concatenation<first_type, second_type> const &other) noexcept {
+    // We can't just assign the other string state, as its start address may be somewhere else on the stack.
+    sz_ptr_t string_start;
+    sz_size_t string_length;
+    sz_string_range(&string_, &string_start, &string_length);
+
+    if (string_length >= other.length()) {
+        sz_string_erase(&string_, other.length(), SZ_SIZE_MAX);
+        other.copy(string_start, other.length());
+    }
+    else {
+        if (!_with_alloc([&](sz_alloc_type &alloc) {
+                string_start = sz_string_expand(&string_, SZ_SIZE_MAX, other.length(), &alloc);
+                if (!string_start) return false;
+                other.copy(string_start, other.length());
+                return true;
+            }))
+            return false;
+    }
+    return true;
+}
+
+template <typename char_type_, typename allocator_>
+bool basic_string<char_type_, allocator_>::try_preparing_replacement(size_type offset, size_type length,
+                                                                     size_type replacement_length) noexcept {
+    // There are three cases:
+    // 1. The replacement is the same length as the replaced range.
+    // 2. The replacement is shorter than the replaced range.
+    // 3. The replacement is longer than the replaced range. An allocation may occur.
+    assert(offset + length <= size());
+
+    // 1. The replacement is the same length as the replaced range.
+    if (replacement_length == length) { return true; }
+
+    // 2. The replacement is shorter than the replaced range.
+    else if (replacement_length < length) {
+        sz_string_erase(&string_, offset + replacement_length, length - replacement_length);
+        return true;
+    }
+    // 3. The replacement is longer than the replaced range. An allocation may occur.
+    else {
+        return _with_alloc([&](sz_alloc_type &alloc) {
+            return sz_string_expand(&string_, offset + length, replacement_length - length, &alloc);
+        });
+    }
+}
+
+/**  @brief  SFINAE-type used to infer the resulting type of concatenating multiple string together. */
+template <typename... args_types>
+struct concatenation_result {};
+
+template <typename first_type, typename second_type>
+struct concatenation_result<first_type, second_type> {
+    using type = concatenation<first_type, second_type>;
+};
+
+template <typename first_type, typename... following_types>
+struct concatenation_result<first_type, following_types...> {
+    using type = concatenation<first_type, typename concatenation_result<following_types...>::type>;
+};
+
+/**
+ *  @brief  Concatenates two strings into a template expression.
+ *  @see    `concatenation` class for more details.
+ */
+template <typename first_type, typename second_type>
+concatenation<first_type, second_type> concatenate(first_type &&first, second_type &&second) noexcept(false) {
+    return {first, second};
+}
+
+/**
+ *  @brief  Concatenates two or more strings into a template expression.
+ *  @see    `concatenation` class for more details.
+ */
+template <typename first_type, typename second_type, typename... following_types>
+typename concatenation_result<first_type, second_type, following_types...>::type concatenate(
+    first_type &&first, second_type &&second, following_types &&...following) noexcept(false) {
+    // Fold expression like the one below would result in faster compile times,
+    // but would incur the penalty of additional `if`-statements in every `append` call.
+    // Moreover, those are only supported in C++17 and later.
+    //      std::size_t total_size = (strings.size() + ... + 0);
+    //      std::string result;
+    //      result.reserve(total_size);
+    //      (result.append(strings), ...);
+    return ashvardanian::stringzilla::concatenate(
+        std::forward<first_type>(first),
+        ashvardanian::stringzilla::concatenate(std::forward<second_type>(second),
+                                               std::forward<following_types>(following)...));
+}
+
+/**
+ *  @brief  Calculates the Levenshtein edit distance between two strings.
+ *  @see    sz_edit_distance
+ */
+template <typename char_type_, typename allocator_type_ = std::allocator<typename std::remove_const<char_type_>::type>>
+std::size_t edit_distance(basic_string_slice<char_type_> const &a, basic_string_slice<char_type_> const &b,
+                          allocator_type_ &&allocator = allocator_type_ {}) noexcept(false) {
+    std::size_t result;
+    if (!_with_alloc(allocator, [&](sz_memory_allocator_t &alloc) {
+            result = sz_edit_distance(a.data(), a.size(), b.data(), b.size(), SZ_SIZE_MAX, &alloc);
+            return result != SZ_SIZE_MAX;
+        }))
+        throw std::bad_alloc();
+    return result;
+}
+
+/**
+ *  @brief  Calculates the Levenshtein edit distance between two strings.
+ *  @see    sz_edit_distance
+ */
+template <typename char_type_, typename allocator_type_ = std::allocator<char_type_>>
+std::size_t edit_distance(basic_string<char_type_, allocator_type_> const &a,
+                          basic_string<char_type_, allocator_type_> const &b) noexcept(false) {
+    return ashvardanian::stringzilla::edit_distance(a.view(), b.view(), a.get_allocator());
+}
+
+/**
+ *  @brief  Calculates the Needleman-Wunsch alignment score between two strings.
+ *  @see    sz_alignment_score
+ */
+template <typename char_type_, typename allocator_type_ = std::allocator<typename std::remove_const<char_type_>::type>>
+std::ptrdiff_t alignment_score(basic_string_slice<char_type_> const &a, basic_string_slice<char_type_> const &b,
+                               std::int8_t const (&subs)[256][256], std::int8_t gap = -1,
+                               allocator_type_ &&allocator = allocator_type_ {}) noexcept(false) {
+
+    static_assert(sizeof(sz_error_cost_t) == sizeof(std::int8_t), "sz_error_cost_t must be 8-bit.");
+    static_assert(std::is_signed<sz_error_cost_t>() == std::is_signed<std::int8_t>(),
+                  "sz_error_cost_t must be signed.");
+
+    std::ptrdiff_t result;
+    if (!_with_alloc(allocator, [&](sz_memory_allocator_t &alloc) {
+            result = sz_alignment_score(a.data(), a.size(), b.data(), b.size(), &subs[0][0], gap, &alloc);
+            return result != SZ_SSIZE_MAX;
+        }))
+        throw std::bad_alloc();
+    return result;
+}
+
+/**
+ *  @brief  Calculates the Needleman-Wunsch alignment score between two strings.
+ *  @see    sz_alignment_score
+ */
+template <typename char_type_, typename allocator_type_ = std::allocator<char_type_>>
+std::ptrdiff_t alignment_score(basic_string<char_type_, allocator_type_> const &a,
+                               basic_string<char_type_, allocator_type_> const &b, //
+                               std::int8_t const (&subs)[256][256], std::int8_t gap = -1) noexcept(false) {
+    return ashvardanian::stringzilla::alignment_score(a.view(), b.view(), subs, gap, a.get_allocator());
+}
+
+/**
+ *  @brief  Overwrites the string slice with random characters from the given alphabet using the random generator.
+ *
+ *  @param  string     The string to overwrite.
+ *  @param  generator  A random generator function object that returns a random number in the range [0, 2^64).
+ *  @param  alphabet   A string of characters to choose from.
+ */
+template <typename char_type_, typename generator_type_>
+void randomize(basic_string_slice<char_type_> string, generator_type_ &generator,
+               string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept {
+    static_assert(!std::is_const<char_type_>::value, "The string must be mutable.");
+    sz_random_generator_t generator_callback = &_call_random_generator<generator_type_>;
+    sz_generate(alphabet.data(), alphabet.size(), string.data(), string.size(), generator_callback, &generator);
+}
+
+/**
+ *  @brief  Overwrites the string slice with random characters from the given alphabet
+ *          using `std::rand` as the random generator.
+ *
+ *  @param  string     The string to overwrite.
+ *  @param  alphabet   A string of characters to choose from.
+ */
+template <typename char_type_>
+void randomize(basic_string_slice<char_type_> string, string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept {
+    randomize(string, &std::rand, alphabet);
+}
+
+/**
+ *  @brief  Internal data-structure used to forward the arguments to the `sz_sort` function.
+ *  @see    sorted_order
+ */
+template <typename objects_type_, typename string_extractor_>
+struct _sequence_args {
+    objects_type_ const *begin;
+    std::size_t count;
+    std::size_t *order;
+    string_extractor_ extractor;
+};
+
+template <typename objects_type_, typename string_extractor_>
+sz_cptr_t _call_sequence_member_start(struct sz_sequence_t const *sequence, sz_size_t i) {
+    using handle_type = _sequence_args<objects_type_, string_extractor_>;
+    handle_type const *args = reinterpret_cast<handle_type const *>(sequence->handle);
+    string_view member = args->extractor(args->begin[i]);
+    return member.data();
+}
+
+template <typename objects_type_, typename string_extractor_>
+sz_size_t _call_sequence_member_length(struct sz_sequence_t const *sequence, sz_size_t i) {
+    using handle_type = _sequence_args<objects_type_, string_extractor_>;
+    handle_type const *args = reinterpret_cast<handle_type const *>(sequence->handle);
+    string_view member = args->extractor(args->begin[i]);
+    return static_cast<sz_size_t>(member.size());
+}
+
+/**
+ *  @brief  Computes the permutation of an array, that would lead to sorted order.
+ *          The elements of the array must be convertible to a `string_view` with the given extractor.
+ *          Unlike the `sz_sort` C interface, overwrites the output array.
+ *
+ *  @param[in] begin       The pointer to the first element of the array.
+ *  @param[in] end         The pointer to the element after the last element of the array.
+ *  @param[out] order      The pointer to the output array of indices, that will be populated with the permutation.
+ *  @param[in] extractor   The function object that extracts the string from the object.
+ *
+ *  @see    sz_sort
+ */
+template <typename objects_type_, typename string_extractor_>
+void sorted_order(objects_type_ const *begin, objects_type_ const *end, std::size_t *order,
+                  string_extractor_ &&extractor) noexcept {
+
+    // Pack the arguments into a single structure to reference it from the callback.
+    _sequence_args<objects_type_, string_extractor_> args = {begin, static_cast<std::size_t>(end - begin), order,
+                                                             std::forward<string_extractor_>(extractor)};
+    // Populate the array with `iota`-style order.
+    for (std::size_t i = 0; i != args.count; ++i) order[i] = i;
+
+    sz_sequence_t array;
+    array.order = reinterpret_cast<sz_u64_t *>(order);
+    array.count = args.count;
+    array.handle = &args;
+    array.get_start = _call_sequence_member_start<objects_type_, string_extractor_>;
+    array.get_length = _call_sequence_member_length<objects_type_, string_extractor_>;
+    sz_sort(&array);
+}
+
+#if !SZ_AVOID_STL
+
+/**
+ *  @brief  Computes the Rabin-Karp-like rolling binary fingerprint of a string.
+ *  @see    sz_hashes
+ */
+template <std::size_t bitset_bits_, typename char_type_>
+void hashes_fingerprint(basic_string_slice<char_type_> const &str, std::size_t window_length,
+                        std::bitset<bitset_bits_> &fingerprint) noexcept {
+    constexpr std::size_t fingerprint_bytes = sizeof(std::bitset<bitset_bits_>);
+    return sz_hashes_fingerprint(str.data(), str.size(), window_length, (sz_ptr_t)&fingerprint, fingerprint_bytes);
+}
+
+/**
+ *  @brief  Computes the Rabin-Karp-like rolling binary fingerprint of a string.
+ *  @see    sz_hashes
+ */
+template <std::size_t bitset_bits_, typename char_type_>
+std::bitset<bitset_bits_> hashes_fingerprint(basic_string_slice<char_type_> const &str,
+                                             std::size_t window_length) noexcept {
+    std::bitset<bitset_bits_> fingerprint;
+    ashvardanian::stringzilla::hashes_fingerprint(str, window_length, fingerprint);
+    return fingerprint;
+}
+
+/**
+ *  @brief  Computes the Rabin-Karp-like rolling binary fingerprint of a string.
+ *  @see    sz_hashes
+ */
+template <std::size_t bitset_bits_, typename char_type_>
+std::bitset<bitset_bits_> hashes_fingerprint(basic_string<char_type_> const &str, std::size_t window_length) noexcept {
+    return ashvardanian::stringzilla::hashes_fingerprint<bitset_bits_>(str.view(), window_length);
+}
+
+/**
+ *  @brief  Computes the permutation of an array, that would lead to sorted order.
+ *  @return The array of indices, that will be populated with the permutation.
+ *  @throw  `std::bad_alloc` if the allocation fails.
+ */
+template <typename objects_type_, typename string_extractor_>
+std::vector<std::size_t> sorted_order(objects_type_ const *begin, objects_type_ const *end,
+                                      string_extractor_ &&extractor) noexcept(false) {
+    std::vector<std::size_t> order(end - begin);
+    sorted_order(begin, end, order.data(), std::forward<string_extractor_>(extractor));
+    return order;
+}
+
+/**
+ *  @brief  Computes the permutation of an array, that would lead to sorted order.
+ *  @return The array of indices, that will be populated with the permutation.
+ *  @throw  `std::bad_alloc` if the allocation fails.
+ */
+template <typename string_like_type_>
+std::vector<std::size_t> sorted_order(string_like_type_ const *begin, string_like_type_ const *end) noexcept(false) {
+    static_assert(std::is_convertible<string_like_type_, string_view>::value,
+                  "The type must be convertible to string_view.");
+    return sorted_order(begin, end, [](string_like_type_ const &s) -> string_view { return s; });
+}
+
+/**
+ *  @brief  Computes the permutation of an array, that would lead to sorted order.
+ *  @return The array of indices, that will be populated with the permutation.
+ *  @throw  `std::bad_alloc` if the allocation fails.
+ */
+template <typename string_like_type_>
+std::vector<std::size_t> sorted_order(std::vector<string_like_type_> const &array) noexcept(false) {
+    static_assert(std::is_convertible<string_like_type_, string_view>::value,
+                  "The type must be convertible to string_view.");
+    return sorted_order(array.data(), array.data() + array.size(),
+                        [](string_like_type_ const &s) -> string_view { return s; });
+}
+
+#endif
+
+} // namespace stringzilla
+} // namespace ashvardanian
+
+#pragma region STL Specializations
+
+namespace std {
+
+template <>
+struct hash<ashvardanian::stringzilla::string_view> {
+    size_t operator()(ashvardanian::stringzilla::string_view str) const noexcept { return str.hash(); }
+};
+
+template <>
+struct hash<ashvardanian::stringzilla::string> {
+    size_t operator()(ashvardanian::stringzilla::string const &str) const noexcept { return str.hash(); }
+};
+
+} // namespace std
+
+#pragma endregion
+
+#endif // STRINGZILLA_HPP_
diff --git a/javascript/lib.c b/javascript/lib.c
index 8ebe72eb..c468c8f8 100644
--- a/javascript/lib.c
+++ b/javascript/lib.c
@@ -7,10 +7,12 @@
  *  @copyright  Copyright (c) 2023
  *  @see        NodeJS docs: https://nodejs.org/api/n-api.html
  */
+#include <stdio.h>  // `printf` for debug builds
+#include <stdlib.h> // `malloc` to export strings into UTF-8
 
-#include <node_api.h>    // `napi_*` functions
-#include <stdlib.h>      // `malloc`
-#include <stringzilla.h> // `sz_*` functions
+#include <node_api.h> // `napi_*` functions
+
+#include <stringzilla/stringzilla.h> // `sz_*` functions
 
 napi_value indexOfAPI(napi_env env, napi_callback_info info) {
     size_t argc = 2;
@@ -35,7 +37,7 @@ napi_value indexOfAPI(napi_env env, napi_callback_info info) {
     napi_value js_result;
     if (needle.length == 0) { napi_create_bigint_int64(env, 0, &js_result); }
     else {
-        sz_string_start_t result = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+        sz_cptr_t result = sz_find(haystack.start, haystack.length, needle.start, needle.length);
 
         // In JavaScript, if `indexOf` is unable to indexOf the specified value, then it should return -1
         if (result == NULL) { napi_create_bigint_int64(env, -1, &js_result); }
@@ -74,12 +76,11 @@ napi_value countAPI(napi_env env, napi_callback_info info) {
 
     size_t count = 0;
     if (needle.length == 0 || haystack.length == 0 || haystack.length < needle.length) { count = 0; }
-    else if (needle.length == 1) { count = sz_count_char(haystack.start, haystack.length, needle.start); }
     else if (overlap) {
         while (haystack.length) {
-            sz_string_start_t ptr = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+            sz_cptr_t ptr = sz_find(haystack.start, haystack.length, needle.start, needle.length);
             sz_bool_t found = ptr != NULL;
-            sz_size_t offset = found ? ptr - haystack.start : haystack.length;
+            sz_size_t offset = found ? (sz_size_t)(ptr - haystack.start) : haystack.length;
             count += found;
             haystack.start += offset + found;
             haystack.length -= offset + found;
@@ -87,9 +88,9 @@ napi_value countAPI(napi_env env, napi_callback_info info) {
     }
     else {
         while (haystack.length) {
-            sz_string_start_t ptr = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+            sz_cptr_t ptr = sz_find(haystack.start, haystack.length, needle.start, needle.length);
             sz_bool_t found = ptr != NULL;
-            sz_size_t offset = found ? ptr - haystack.start : haystack.length;
+            sz_size_t offset = found ? (sz_size_t)(ptr - haystack.start) : haystack.length;
             count += found;
             haystack.start += offset + needle.length;
             haystack.length -= offset + needle.length * found;
diff --git a/levenshtein.ipynb b/levenshtein.ipynb
new file mode 100644
index 00000000..38367f36
--- /dev/null
+++ b/levenshtein.ipynb
@@ -0,0 +1,374 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import random"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exploring the Impact of Evaluation Order on the Wagner Fisher Algorithm for Levenshtein Edit Distance"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def algo_v0(s1, s2) -> int:\n",
+    "    # Create a matrix of size (len(s1)+1) x (len(s2)+1)\n",
+    "    matrix = np.zeros((len(s1) + 1, len(s2) + 1), dtype=int)\n",
+    "\n",
+    "    # Initialize the first column and first row of the matrix\n",
+    "    for i in range(len(s1) + 1):\n",
+    "        matrix[i, 0] = i\n",
+    "    for j in range(len(s2) + 1):\n",
+    "        matrix[0, j] = j\n",
+    "\n",
+    "    # Compute Levenshtein distance\n",
+    "    for i in range(1, len(s1) + 1):\n",
+    "        for j in range(1, len(s2) + 1):\n",
+    "            substitution_cost = s1[i - 1] != s2[j - 1]\n",
+    "            matrix[i, j] = min(\n",
+    "                matrix[i - 1, j] + 1,  # Deletion\n",
+    "                matrix[i, j - 1] + 1,  # Insertion\n",
+    "                matrix[i - 1, j - 1] + substitution_cost,  # Substitution\n",
+    "            )\n",
+    "\n",
+    "    # Return the Levenshtein distance\n",
+    "    return matrix[len(s1), len(s2)], matrix"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Accelerating this exact algorithm isn't trivial, is the `matrix[i, j]` value has a dependency on the `matrix[i, j-1]` value.\n",
+    "So we can't brute-force accelerate the inner loop.\n",
+    "Instead, we can show that we can evaluate the matrix in a different order, and still get the same result."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "![](https://mathworld.wolfram.com/images/eps-svg/SkewDiagonal_1000.svg)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def algo_v1(s1, s2, verbose: bool = False) -> int:\n",
+    "    assert len(s1) == len(s2), \"First define an algo for square matrices!\"\n",
+    "    # Create a matrix of size (len(s1)+1) x (len(s2)+1)\n",
+    "    matrix = np.zeros((len(s1) + 1, len(s2) + 1), dtype=int)\n",
+    "    matrix[:, :] = 99\n",
+    "\n",
+    "    # Initialize the first column and first row of the matrix\n",
+    "    for i in range(len(s1) + 1):\n",
+    "        matrix[i, 0] = i\n",
+    "    for j in range(len(s2) + 1):\n",
+    "        matrix[0, j] = j\n",
+    "\n",
+    "    # Number of rows and columns in the square matrix.\n",
+    "    n = len(s1) + 1\n",
+    "    skew_diagonals_count = 2 * n - 1\n",
+    "    # Compute Levenshtein distance\n",
+    "    for skew_diagonal_idx in range(2, skew_diagonals_count):\n",
+    "        skew_diagonal_length = (skew_diagonal_idx + 1) if skew_diagonal_idx < n else (2*n - skew_diagonal_idx - 1)\n",
+    "        for offset_within_skew_diagonal in range(skew_diagonal_length):\n",
+    "            if skew_diagonal_idx < n:\n",
+    "                # If we passed the main skew diagonal yet, \n",
+    "                # Then we have to skip the first and the last operation,\n",
+    "                # as those are already pre-populated and form the first column \n",
+    "                # and the first row of the Levenshtein matrix respectively.\n",
+    "                if offset_within_skew_diagonal == 0 or offset_within_skew_diagonal + 1 == skew_diagonal_length:\n",
+    "                    continue      \n",
+    "                i = skew_diagonal_idx - offset_within_skew_diagonal\n",
+    "                j = offset_within_skew_diagonal\n",
+    "                if verbose:\n",
+    "                    print(f\"top left triangle: {skew_diagonal_idx=}, {skew_diagonal_length=}, {i=}, {j=}\")\n",
+    "            else:\n",
+    "                i = n - offset_within_skew_diagonal - 1\n",
+    "                j = skew_diagonal_idx - n + offset_within_skew_diagonal + 1\n",
+    "                if verbose:\n",
+    "                    print(f\"bottom right triangle: {skew_diagonal_idx=}, {skew_diagonal_length=}, {i=}, {j=}\")\n",
+    "            substitution_cost = s1[i - 1] != s2[j - 1]\n",
+    "            matrix[i, j] = min(\n",
+    "                matrix[i - 1, j] + 1,  # Deletion\n",
+    "                matrix[i, j - 1] + 1,  # Insertion\n",
+    "                matrix[i - 1, j - 1] + substitution_cost,  # Substitution\n",
+    "            )\n",
+    "\n",
+    "    # Return the Levenshtein distance\n",
+    "    return matrix[len(s1), len(s2)], matrix"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's generate some random strings and make sure we produce the right result."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for _ in range(10):\n",
+    "    s1 = ''.join(random.choices(\"ab\", k=50))\n",
+    "    s2 = ''.join(random.choices(\"ab\", k=50))\n",
+    "    d0, _ = algo_v0(s1, s2)\n",
+    "    d1, _ = algo_v1(s1, s2)\n",
+    "    assert d0 == d1 "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Going further, we can avoid storing the whole matrix, and only store two diagonals at a time.\n",
+    "The longer will never exceed N. The shorter one is always at most N-1, and is always shorter by one."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('listen',\n",
+       " 'silent',\n",
+       " 'distance = 4',\n",
+       " array([[0, 1, 2, 3, 4, 5, 6],\n",
+       "        [1, 1, 2, 2, 3, 4, 5],\n",
+       "        [2, 2, 1, 2, 3, 4, 5],\n",
+       "        [3, 2, 2, 2, 3, 4, 5],\n",
+       "        [4, 3, 3, 3, 3, 4, 4],\n",
+       "        [5, 4, 4, 4, 3, 4, 5],\n",
+       "        [6, 5, 5, 5, 4, 3, 4]]))"
+      ]
+     },
+     "execution_count": 29,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "s1 = \"listen\"\n",
+    "s2 = \"silent\"\n",
+    "# s1 = ''.join(random.choices(\"abcd\", k=100))\n",
+    "# s2 = ''.join(random.choices(\"abcd\", k=100))\n",
+    "distance, baseline = algo_v0(s1, s2)\n",
+    "s1, s2, f\"{distance = }\", baseline"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(array([0, 0, 0, 0, 0, 0, 0], dtype=uint64),\n",
+       " array([1, 1, 0, 0, 0, 0, 0], dtype=uint64),\n",
+       " array([0, 0, 0, 0, 0, 0, 0], dtype=uint64))"
+      ]
+     },
+     "execution_count": 30,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "assert len(s1) == len(s2), \"First define an algo for square matrices!\"\n",
+    "# Number of rows and columns in the square matrix.\n",
+    "n = len(s1) + 1\n",
+    "\n",
+    "# Let's use just a couple of arrays to store the previous skew diagonals.\n",
+    "# Let's imagine that our Levenshtein matrix is gonna have 5x5 size for two words of length 4.\n",
+    "#         B C D E << s2 characters: BCDE\n",
+    "#     + ---------\n",
+    "#     | a b c d e\n",
+    "#   F | f g h i j\n",
+    "#   K | k l m n o\n",
+    "#   P | p q r s t\n",
+    "#   U | u v w x y\n",
+    "#   ^\n",
+    "#   ^ s1 characters: FKPU\n",
+    "following = np.zeros(n, dtype=np.uint) # let's assume we are computing the main skew diagonal: [u, q, m, i, e]\n",
+    "current = np.zeros(n, dtype=np.uint) # will contain: [p, l, h, e]\n",
+    "previous = np.zeros(n, dtype=np.uint) # will contain: [k, g, c]\n",
+    "\n",
+    "# Initialize the first two diagonals.\n",
+    "# The `previous` would contain the values [a].\n",
+    "# The `current` would contain the values [f, b]. \n",
+    "previous[0] = 0\n",
+    "current[0:2] = 1\n",
+    "previous, current, following"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To feel safer, while designing our alternative traversal algorithm, let's define an extraction function, that will get the values of a certain skewed diagonal."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def get_skewed_diagonal(matrix: np.ndarray, index: int):\n",
+    "    flipped_matrix = np.fliplr(matrix)\n",
+    "    return np.flip(np.diag(flipped_matrix, k= matrix.shape[1] - index - 1))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "matrix = np.array([[1, 2, 3],\n",
+    "                   [4, 5, 6],\n",
+    "                   [7, 8, 9]])\n",
+    "assert np.all(get_skewed_diagonal(matrix, 2) == [7, 5, 3])\n",
+    "assert np.all(get_skewed_diagonal(matrix, 1) == [4, 2])\n",
+    "assert np.all(get_skewed_diagonal(matrix, 4) == [9])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(array([5, 3, 2, 2, 3, 5, 0], dtype=uint64),\n",
+       " array([6, 4, 3, 2, 3, 4, 6], dtype=uint64),\n",
+       " array([6, 4, 3, 2, 3, 4, 6], dtype=uint64))"
+      ]
+     },
+     "execution_count": 33,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# To evaluate every subsequent entry:\n",
+    "following_skew_diagonal_idx = 2\n",
+    "while following_skew_diagonal_idx < n:\n",
+    "    following_skew_diagonal_length = following_skew_diagonal_idx + 1\n",
+    "\n",
+    "    old_substitution_costs = previous[:following_skew_diagonal_length - 2]\n",
+    "    added_substitution_costs = [s1[following_skew_diagonal_idx - i - 2] != s2[i] for i in range(following_skew_diagonal_length - 2)]\n",
+    "    substitution_costs = old_substitution_costs + added_substitution_costs\n",
+    "\n",
+    "    following[1:following_skew_diagonal_length-1] = np.minimum(current[1:following_skew_diagonal_length-1] + 1, current[:following_skew_diagonal_length-2] + 1) # Insertions or deletions\n",
+    "    following[1:following_skew_diagonal_length-1] = np.minimum(following[1:following_skew_diagonal_length-1], substitution_costs) # Substitutions\n",
+    "    following[0] = following_skew_diagonal_idx\n",
+    "    following[following_skew_diagonal_length-1] = following_skew_diagonal_idx\n",
+    "    assert np.all(following[:following_skew_diagonal_length] == get_skewed_diagonal(baseline, following_skew_diagonal_idx))\n",
+    "    \n",
+    "    previous[:] = current[:]\n",
+    "    current[:] = following[:]\n",
+    "    following_skew_diagonal_idx += 1\n",
+    "\n",
+    "previous, current, following # Log the state"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "By now we've scanned through the upper triangle of the matrix, where each subsequent iteration results in a larger diagonal. From now onwards, we will be shrinking. Instead of adding value equal to the skewed diagonal index on either side, we will be cropping those values out."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(array([5, 4, 5, 5, 5, 6, 0], dtype=uint64),\n",
+       " array([4, 5, 4, 5, 5, 5, 6], dtype=uint64),\n",
+       " array([4, 5, 4, 5, 5, 5, 6], dtype=uint64))"
+      ]
+     },
+     "execution_count": 34,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "while following_skew_diagonal_idx < 2 * n - 1:\n",
+    "    following_skew_diagonal_length = 2 * n - 1 - following_skew_diagonal_idx\n",
+    "    old_substitution_costs = previous[:following_skew_diagonal_length]\n",
+    "    added_substitution_costs = [s1[len(s1) - i - 1] != s2[following_skew_diagonal_idx - n + i] for i in range(following_skew_diagonal_length)]\n",
+    "    substitution_costs = old_substitution_costs + added_substitution_costs\n",
+    "    \n",
+    "    following[:following_skew_diagonal_length] = np.minimum(current[:following_skew_diagonal_length] + 1, current[1:following_skew_diagonal_length+1] + 1) # Insertions or deletions\n",
+    "    following[:following_skew_diagonal_length] = np.minimum(following[:following_skew_diagonal_length], substitution_costs) # Substitutions\n",
+    "    assert np.all(following[:following_skew_diagonal_length] == get_skewed_diagonal(baseline, following_skew_diagonal_idx)), f\"\\n{following[:following_skew_diagonal_length]} not equal to \\n{get_skewed_diagonal(baseline, following_skew_diagonal_idx)}\"\n",
+    "    \n",
+    "    previous[:following_skew_diagonal_length] = current[1:following_skew_diagonal_length+1]\n",
+    "    current[:following_skew_diagonal_length] = following[:following_skew_diagonal_length]\n",
+    "    following_skew_diagonal_idx += 1\n",
+    "\n",
+    "previous, current, following # Log the state"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 35,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert distance == following[0], f\"{distance = } != {following[0] = }\""
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "base",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/package.json b/package.json
index 8a65b966..789ada66 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "stringzilla",
-  "version": "2.0.3",
-  "description": "Crunch multi-gigabyte strings with ease",
+  "version": "2.0.4",
+  "description": "SIMD-accelerated string search, sort, hashes, fingerprints, & edit distances",
   "author": "Ash Vardanian",
   "license": "Apache 2.0",
   "main": "javascript/stringzilla.js",
diff --git a/python/lib.c b/python/lib.c
index b6ac31ae..8e0a7dad 100644
--- a/python/lib.c
+++ b/python/lib.c
@@ -23,16 +23,23 @@
 #include <BaseTsd.h>
 typedef SSIZE_T ssize_t;
 #else
+#include <limits.h> // `SSIZE_MAX`
 #include <unistd.h> // `ssize_t`
 #endif
 
+// It seems like some Python versions forget to include a header, so we should:
+// https://github.com/ashvardanian/StringZilla/actions/runs/7706636733/job/21002535521
+#ifndef SSIZE_MAX
+#define SSIZE_MAX (SIZE_MAX / 2)
+#endif
+
 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
 #include <Python.h>            // Core CPython interfaces
 #include <numpy/arrayobject.h> // NumPy
 
 #include <string.h> // `memset`, `memcpy`
 
-#include <stringzilla.h>
+#include <stringzilla/stringzilla.h>
 
 #pragma region Forward Declarations
 
@@ -54,7 +61,7 @@ typedef struct {
 #else
         int file_descriptor;
 #endif
-    sz_string_start_t start;
+    sz_cptr_t start;
     sz_size_t length;
 } File;
 
@@ -73,7 +80,7 @@ typedef struct {
 typedef struct {
     PyObject_HEAD //
         PyObject *parent;
-    sz_string_start_t start;
+    sz_cptr_t start;
     sz_size_t length;
 } Str;
 
@@ -144,11 +151,26 @@ typedef struct {
 
 #pragma region Helpers
 
-inline static sz_string_start_t parts_get_start(sz_sequence_t *seq, sz_size_t i) {
+static sz_ptr_t temporary_memory_allocate(sz_size_t size, sz_string_view_t *existing) {
+    if (existing->length < size) {
+        sz_cptr_t new_start = realloc(existing->start, size);
+        if (!new_start) {
+            PyErr_Format(PyExc_MemoryError, "Unable to allocate memory for the Levenshtein matrix");
+            return NULL;
+        }
+        existing->start = new_start;
+        existing->length = size;
+    }
+    return existing->start;
+}
+
+static void temporary_memory_free(sz_ptr_t start, sz_size_t size, sz_string_view_t *existing) {}
+
+static sz_cptr_t parts_get_start(sz_sequence_t *seq, sz_size_t i) {
     return ((sz_string_view_t const *)seq->handle)[i].start;
 }
 
-inline static sz_size_t parts_get_length(sz_sequence_t *seq, sz_size_t i) {
+static sz_size_t parts_get_length(sz_sequence_t *seq, sz_size_t i) {
     return ((sz_string_view_t const *)seq->handle)[i].length;
 }
 
@@ -187,28 +209,7 @@ void apply_order(sz_string_view_t *array, sz_u64_t *order, size_t length) {
     }
 }
 
-void slice(size_t length, ssize_t start, ssize_t end, size_t *normalized_offset, size_t *normalized_length) {
-
-    // clang-format off
-    // Normalize negative indices
-    if (start < 0) start += length;
-    if (end < 0) end += length;
-
-    // Clamp indices to a valid range
-    if (start < 0) start = 0;
-    if (end < 0) end = 0;
-    if (start > (ssize_t)length) start = length;
-    if (end > (ssize_t)length) end = length;
-
-    // Ensure start <= end
-    if (start > end) start = end;
-    // clang-format on
-
-    *normalized_offset = start;
-    *normalized_length = end - start;
-}
-
-sz_bool_t export_string_like(PyObject *object, sz_string_start_t **start, sz_size_t *length) {
+sz_bool_t export_string_like(PyObject *object, sz_cptr_t **start, sz_size_t *length) {
     if (PyUnicode_Check(object)) {
         // Handle Python str
         Py_ssize_t signed_length;
@@ -243,8 +244,8 @@ sz_bool_t export_string_like(PyObject *object, sz_string_start_t **start, sz_siz
 
 typedef void (*get_string_at_offset_t)(Strs *, Py_ssize_t, Py_ssize_t, PyObject **, char const **, size_t *);
 
-void str_at_offset_consecutive_32bit(
-    Strs *strs, Py_ssize_t i, Py_ssize_t count, PyObject **parent, char const **start, size_t *length) {
+void str_at_offset_consecutive_32bit(Strs *strs, Py_ssize_t i, Py_ssize_t count, PyObject **parent, char const **start,
+                                     size_t *length) {
     uint32_t start_offset = (i == 0) ? 0 : strs->data.consecutive_32bit.end_offsets[i - 1];
     uint32_t end_offset = strs->data.consecutive_32bit.end_offsets[i];
     *start = strs->data.consecutive_32bit.start + start_offset;
@@ -252,8 +253,8 @@ void str_at_offset_consecutive_32bit(
     *parent = strs->data.consecutive_32bit.parent;
 }
 
-void str_at_offset_consecutive_64bit(
-    Strs *strs, Py_ssize_t i, Py_ssize_t count, PyObject **parent, char const **start, size_t *length) {
+void str_at_offset_consecutive_64bit(Strs *strs, Py_ssize_t i, Py_ssize_t count, PyObject **parent, char const **start,
+                                     size_t *length) {
     uint64_t start_offset = (i == 0) ? 0 : strs->data.consecutive_64bit.end_offsets[i - 1];
     uint64_t end_offset = strs->data.consecutive_64bit.end_offsets[i];
     *start = strs->data.consecutive_64bit.start + start_offset;
@@ -261,8 +262,8 @@ void str_at_offset_consecutive_64bit(
     *parent = strs->data.consecutive_64bit.parent;
 }
 
-void str_at_offset_reordered(
-    Strs *strs, Py_ssize_t i, Py_ssize_t count, PyObject **parent, char const **start, size_t *length) {
+void str_at_offset_reordered(Strs *strs, Py_ssize_t i, Py_ssize_t count, PyObject **parent, char const **start,
+                             size_t *length) {
     *start = strs->data.reordered.parts[i].start;
     *length = strs->data.reordered.parts[i].length;
     *parent = strs->data.reordered.parent;
@@ -540,7 +541,7 @@ static int Str_init(Str *self, PyObject *args, PyObject *kwargs) {
 
     // Apply slicing
     size_t normalized_offset, normalized_length;
-    slice(self->length, from, to, &normalized_offset, &normalized_length);
+    sz_ssize_clamp_interval(self->length, from, to, &normalized_offset, &normalized_length);
     self->start = ((char *)self->start) + normalized_offset;
     self->length = normalized_length;
     return 0;
@@ -569,7 +570,29 @@ static void Str_dealloc(Str *self) {
 
 static PyObject *Str_str(Str *self) { return PyUnicode_FromStringAndSize(self->start, self->length); }
 
-static Py_hash_t Str_hash(Str *self) { return (Py_hash_t)sz_hash_crc32(self->start, self->length); }
+static Py_hash_t Str_hash(Str *self) { return (Py_hash_t)sz_hash(self->start, self->length); }
+
+static PyObject *Str_like_hash(PyObject *self, PyObject *args, PyObject *kwargs) {
+    // Check minimum arguments
+    int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
+    Py_ssize_t nargs = PyTuple_Size(args);
+    if (nargs < !is_member || nargs > !is_member + 1 || kwargs) {
+        PyErr_SetString(PyExc_TypeError, "hash() expects exactly one positional argument");
+        return NULL;
+    }
+
+    PyObject *text_obj = is_member ? self : PyTuple_GET_ITEM(args, 0);
+    sz_string_view_t text;
+
+    // Validate and convert `text`
+    if (!export_string_like(text_obj, &text.start, &text.length)) {
+        PyErr_SetString(PyExc_TypeError, "The text argument must be string-like");
+        return NULL;
+    }
+
+    sz_u64_t result = sz_hash(text.start, text.length);
+    return PyLong_FromSize_t((size_t)result);
+}
 
 static Py_ssize_t Str_len(Str *self) { return self->length; }
 
@@ -655,7 +678,7 @@ static int Str_in(Str *self, PyObject *arg) {
         return -1;
     }
 
-    return sz_find_substring(self->start, self->length, needle_struct.start, needle_struct.length) != NULL;
+    return sz_find(self->start, self->length, needle_struct.start, needle_struct.length) != NULL;
 }
 
 static Py_ssize_t Strs_len(Strs *self) {
@@ -717,44 +740,37 @@ static PyObject *Strs_subscript(Strs *self, PyObject *key) {
         // Depending on the layout, the procedure will be different.
         self_slice->type = self->type;
         switch (self->type) {
-        case STRS_CONSECUTIVE_32: {
-            struct consecutive_slices_32bit_t *from = &self->data.consecutive_32bit;
-            struct consecutive_slices_32bit_t *to = &self_slice->data.consecutive_32bit;
-            to->count = stop - start;
-            to->separator_length = from->separator_length;
-            to->parent = from->parent;
 
-            size_t first_length;
-            str_at_offset_consecutive_32bit(self, start, count, &to->parent, &to->start, &first_length);
-            uint32_t first_offset = to->start - from->start;
-            to->end_offsets = malloc(sizeof(uint32_t) * to->count);
-            if (to->end_offsets == NULL && PyErr_NoMemory()) {
-                Py_XDECREF(self_slice);
-                return NULL;
-            }
-            for (size_t i = 0; i != to->count; ++i) to->end_offsets[i] = from->end_offsets[i] - first_offset;
-            Py_INCREF(to->parent);
+/* Usable as consecutive_logic(64bit), e.g. */
+#define consecutive_logic(type)                                                                               \
+    typedef index_##type##_t index_t;                                                                         \
+    typedef struct consecutive_slices_##type##_t slice_t;                                                     \
+    slice_t *from = &self->data.consecutive_##type;                                                           \
+    slice_t *to = &self_slice->data.consecutive_##type;                                                       \
+    to->count = stop - start;                                                                                 \
+    to->separator_length = from->separator_length;                                                            \
+    to->parent = from->parent;                                                                                \
+    size_t first_length;                                                                                      \
+    str_at_offset_consecutive_##type(self, start, count, &to->parent, &to->start, &first_length);             \
+    index_t first_offset = to->start - from->start;                                                           \
+    to->end_offsets = malloc(sizeof(index_t) * to->count);                                                    \
+    if (to->end_offsets == NULL && PyErr_NoMemory()) {                                                        \
+        Py_XDECREF(self_slice);                                                                               \
+        return NULL;                                                                                          \
+    }                                                                                                         \
+    for (size_t i = 0; i != to->count; ++i) to->end_offsets[i] = from->end_offsets[i + start] - first_offset; \
+    Py_INCREF(to->parent);
+        case STRS_CONSECUTIVE_32: {
+            typedef uint32_t index_32bit_t;
+            consecutive_logic(32bit);
             break;
         }
         case STRS_CONSECUTIVE_64: {
-            struct consecutive_slices_64bit_t *from = &self->data.consecutive_64bit;
-            struct consecutive_slices_64bit_t *to = &self_slice->data.consecutive_64bit;
-            to->count = stop - start;
-            to->separator_length = from->separator_length;
-            to->parent = from->parent;
-
-            size_t first_length;
-            str_at_offset_consecutive_64bit(self, start, count, &to->parent, &to->start, &first_length);
-            uint64_t first_offset = to->start - from->start;
-            to->end_offsets = malloc(sizeof(uint64_t) * to->count);
-            if (to->end_offsets == NULL && PyErr_NoMemory()) {
-                Py_XDECREF(self_slice);
-                return NULL;
-            }
-            for (size_t i = 0; i != to->count; ++i) to->end_offsets[i] = from->end_offsets[i] - first_offset;
-            Py_INCREF(to->parent);
+            typedef uint64_t index_64bit_t;
+            consecutive_logic(64bit);
             break;
         }
+#undef consecutive_logic
         case STRS_REORDERED: {
             struct reordered_slices_t *from = &self->data.reordered;
             struct reordered_slices_t *to = &self_slice->data.reordered;
@@ -790,7 +806,7 @@ static int Strs_contains(Str *self, PyObject *arg) { return 0; }
 
 static PyObject *Str_richcompare(PyObject *self, PyObject *other, int op) {
 
-    sz_string_start_t a_start = NULL, b_start = NULL;
+    sz_cptr_t a_start = NULL, b_start = NULL;
     sz_size_t a_length = 0, b_length = 0;
     if (!export_string_like(self, &a_start, &a_length) || !export_string_like(other, &b_start, &b_length))
         Py_RETURN_NOTIMPLEMENTED;
@@ -814,15 +830,12 @@ static PyObject *Str_richcompare(PyObject *self, PyObject *other, int op) {
 }
 
 /**
+ *  @brief  Implementation function for all search-like operations, parameterized by a function callback.
  *  @return 1 on success, 0 on failure.
  */
-static int Str_find_( //
-    PyObject *self,
-    PyObject *args,
-    PyObject *kwargs,
-    Py_ssize_t *offset_out,
-    sz_string_view_t *haystack_out,
-    sz_string_view_t *needle_out) {
+static int _Str_find_implementation_( //
+    PyObject *self, PyObject *args, PyObject *kwargs, sz_find_t finder, Py_ssize_t *offset_out,
+    sz_string_view_t *haystack_out, sz_string_view_t *needle_out) {
 
     int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
     Py_ssize_t nargs = PyTuple_Size(args);
@@ -883,25 +896,34 @@ static int Str_find_( //
 
     // Limit the `haystack` range
     size_t normalized_offset, normalized_length;
-    slice(haystack.length, start, end, &normalized_offset, &normalized_length);
+    sz_ssize_clamp_interval(haystack.length, start, end, &normalized_offset, &normalized_length);
     haystack.start += normalized_offset;
     haystack.length = normalized_length;
 
     // Perform contains operation
-    sz_string_start_t match = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+    sz_cptr_t match = finder(haystack.start, haystack.length, needle.start, needle.length);
     if (match == NULL) { *offset_out = -1; }
-    else { *offset_out = (Py_ssize_t)(match - haystack.start); }
+    else { *offset_out = (Py_ssize_t)(match - haystack.start + normalized_offset); }
 
     *haystack_out = haystack;
     *needle_out = needle;
     return 1;
 }
 
+static PyObject *Str_contains(PyObject *self, PyObject *args, PyObject *kwargs) {
+    Py_ssize_t signed_offset;
+    sz_string_view_t text;
+    sz_string_view_t separator;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_find, &signed_offset, &text, &separator)) return NULL;
+    if (signed_offset == -1) { Py_RETURN_FALSE; }
+    else { Py_RETURN_TRUE; }
+}
+
 static PyObject *Str_find(PyObject *self, PyObject *args, PyObject *kwargs) {
     Py_ssize_t signed_offset;
     sz_string_view_t text;
     sz_string_view_t separator;
-    if (!Str_find_(self, args, kwargs, &signed_offset, &text, &separator)) return NULL;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_find, &signed_offset, &text, &separator)) return NULL;
     return PyLong_FromSsize_t(signed_offset);
 }
 
@@ -909,7 +931,7 @@ static PyObject *Str_index(PyObject *self, PyObject *args, PyObject *kwargs) {
     Py_ssize_t signed_offset;
     sz_string_view_t text;
     sz_string_view_t separator;
-    if (!Str_find_(self, args, kwargs, &signed_offset, &text, &separator)) return NULL;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_find, &signed_offset, &text, &separator)) return NULL;
     if (signed_offset == -1) {
         PyErr_SetString(PyExc_ValueError, "substring not found");
         return NULL;
@@ -917,23 +939,34 @@ static PyObject *Str_index(PyObject *self, PyObject *args, PyObject *kwargs) {
     return PyLong_FromSsize_t(signed_offset);
 }
 
-static PyObject *Str_contains(PyObject *self, PyObject *args, PyObject *kwargs) {
+static PyObject *Str_rfind(PyObject *self, PyObject *args, PyObject *kwargs) {
     Py_ssize_t signed_offset;
     sz_string_view_t text;
     sz_string_view_t separator;
-    if (!Str_find_(self, args, kwargs, &signed_offset, &text, &separator)) return NULL;
-    if (signed_offset == -1) { Py_RETURN_FALSE; }
-    else { Py_RETURN_TRUE; }
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_rfind, &signed_offset, &text, &separator)) return NULL;
+    return PyLong_FromSsize_t(signed_offset);
 }
 
-static PyObject *Str_partition(PyObject *self, PyObject *args, PyObject *kwargs) {
+static PyObject *Str_rindex(PyObject *self, PyObject *args, PyObject *kwargs) {
+    Py_ssize_t signed_offset;
+    sz_string_view_t text;
+    sz_string_view_t separator;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_rfind, &signed_offset, &text, &separator)) return NULL;
+    if (signed_offset == -1) {
+        PyErr_SetString(PyExc_ValueError, "substring not found");
+        return NULL;
+    }
+    return PyLong_FromSsize_t(signed_offset);
+}
+
+static PyObject *_Str_partition_implementation(PyObject *self, PyObject *args, PyObject *kwargs, sz_find_t finder) {
     Py_ssize_t separator_index;
     sz_string_view_t text;
     sz_string_view_t separator;
     PyObject *result_tuple;
 
-    // Use Str_find_ to get the index of the separator
-    if (!Str_find_(self, args, kwargs, &separator_index, &text, &separator)) return NULL;
+    // Use _Str_find_implementation_ to get the index of the separator
+    if (!_Str_find_implementation_(self, args, kwargs, finder, &separator_index, &text, &separator)) return NULL;
 
     // If separator is not found, return a tuple (self, "", "")
     if (separator_index == -1) {
@@ -972,6 +1005,14 @@ static PyObject *Str_partition(PyObject *self, PyObject *args, PyObject *kwargs)
     return result_tuple;
 }
 
+static PyObject *Str_partition(PyObject *self, PyObject *args, PyObject *kwargs) {
+    return _Str_partition_implementation(self, args, kwargs, &sz_find);
+}
+
+static PyObject *Str_rpartition(PyObject *self, PyObject *args, PyObject *kwargs) {
+    return _Str_partition_implementation(self, args, kwargs, &sz_rfind);
+}
+
 static PyObject *Str_count(PyObject *self, PyObject *args, PyObject *kwargs) {
     int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
     Py_ssize_t nargs = PyTuple_Size(args);
@@ -1010,16 +1051,15 @@ static PyObject *Str_count(PyObject *self, PyObject *args, PyObject *kwargs) {
     if ((start == -1 || end == -1 || allowoverlap == -1) && PyErr_Occurred()) return NULL;
 
     size_t normalized_offset, normalized_length;
-    slice(haystack.length, start, end, &normalized_offset, &normalized_length);
+    sz_ssize_clamp_interval(haystack.length, start, end, &normalized_offset, &normalized_length);
     haystack.start += normalized_offset;
     haystack.length = normalized_length;
 
     size_t count = 0;
     if (needle.length == 0 || haystack.length == 0 || haystack.length < needle.length) { count = 0; }
-    else if (needle.length == 1) { count = sz_count_char(haystack.start, haystack.length, needle.start); }
     else if (allowoverlap) {
         while (haystack.length) {
-            sz_string_start_t ptr = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+            sz_cptr_t ptr = sz_find(haystack.start, haystack.length, needle.start, needle.length);
             sz_bool_t found = ptr != NULL;
             sz_size_t offset = found ? ptr - haystack.start : haystack.length;
             count += found;
@@ -1029,7 +1069,7 @@ static PyObject *Str_count(PyObject *self, PyObject *args, PyObject *kwargs) {
     }
     else {
         while (haystack.length) {
-            sz_string_start_t ptr = sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
+            sz_cptr_t ptr = sz_find(haystack.start, haystack.length, needle.start, needle.length);
             sz_bool_t found = ptr != NULL;
             sz_size_t offset = found ? ptr - haystack.start : haystack.length;
             count += found;
@@ -1041,7 +1081,7 @@ static PyObject *Str_count(PyObject *self, PyObject *args, PyObject *kwargs) {
     return PyLong_FromSize_t(count);
 }
 
-static PyObject *Str_levenstein(PyObject *self, PyObject *args, PyObject *kwargs) {
+static PyObject *Str_edit_distance(PyObject *self, PyObject *args, PyObject *kwargs) {
     int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
     Py_ssize_t nargs = PyTuple_Size(args);
     if (nargs < !is_member + 1 || nargs > !is_member + 2) {
@@ -1066,9 +1106,9 @@ static PyObject *Str_levenstein(PyObject *self, PyObject *args, PyObject *kwargs
             }
     }
 
-    int bound = 255; // Default value for bound
-    if (bound_obj && ((bound = PyLong_AsLong(bound_obj)) > 255 || bound < 0)) {
-        PyErr_Format(PyExc_ValueError, "Bound must be an integer between 0 and 255");
+    Py_ssize_t bound = 0; // Default value for bound
+    if (bound_obj && ((bound = PyLong_AsSsize_t(bound_obj)) < 0)) {
+        PyErr_Format(PyExc_ValueError, "Bound must be a non-negative integer");
         return NULL;
     }
 
@@ -1079,22 +1119,118 @@ static PyObject *Str_levenstein(PyObject *self, PyObject *args, PyObject *kwargs
         return NULL;
     }
 
-    // Allocate memory for the Levenstein matrix
-    size_t memory_needed = sz_levenstein_memory_needed(str1.length, str2.length);
-    if (temporary_memory.length < memory_needed) {
-        temporary_memory.start = realloc(temporary_memory.start, memory_needed);
-        temporary_memory.length = memory_needed;
+    // Allocate memory for the Levenshtein matrix
+    sz_memory_allocator_t reusing_allocator;
+    reusing_allocator.allocate = &temporary_memory_allocate;
+    reusing_allocator.free = &temporary_memory_free;
+    reusing_allocator.handle = &temporary_memory;
+
+    sz_size_t distance =
+        sz_edit_distance(str1.start, str1.length, str2.start, str2.length, (sz_size_t)bound, &reusing_allocator);
+
+    // Check for memory allocation issues
+    if (distance == SZ_SIZE_MAX) {
+        PyErr_NoMemory();
+        return NULL;
     }
-    if (!temporary_memory.start) {
-        PyErr_Format(PyExc_MemoryError, "Unable to allocate memory for the Levenshtein matrix");
+
+    return PyLong_FromSize_t(distance);
+}
+
+static PyObject *Str_alignment_score(PyObject *self, PyObject *args, PyObject *kwargs) {
+    int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
+    Py_ssize_t nargs = PyTuple_Size(args);
+    if (nargs < !is_member + 1 || nargs > !is_member + 2) {
+        PyErr_Format(PyExc_TypeError, "Invalid number of arguments");
+        return NULL;
+    }
+
+    PyObject *str1_obj = is_member ? self : PyTuple_GET_ITEM(args, 0);
+    PyObject *str2_obj = PyTuple_GET_ITEM(args, !is_member + 0);
+    PyObject *substitutions_obj = nargs > !is_member + 1 ? PyTuple_GET_ITEM(args, !is_member + 1) : NULL;
+    PyObject *gap_obj = nargs > !is_member + 2 ? PyTuple_GET_ITEM(args, !is_member + 2) : NULL;
+
+    if (kwargs) {
+        PyObject *key, *value;
+        Py_ssize_t pos = 0;
+        while (PyDict_Next(kwargs, &pos, &key, &value))
+            if (PyUnicode_CompareWithASCIIString(key, "gap_score") == 0) {
+                if (gap_obj) {
+                    PyErr_Format(PyExc_TypeError, "Received the `gap_score` both as positional and keyword argument");
+                    return NULL;
+                }
+                gap_obj = value;
+            }
+            else if (PyUnicode_CompareWithASCIIString(key, "substitution_matrix") == 0) {
+                if (substitutions_obj) {
+                    PyErr_Format(PyExc_TypeError,
+                                 "Received the `substitution_matrix` both as positional and keyword argument");
+                    return NULL;
+                }
+                substitutions_obj = value;
+            }
+    }
+
+    Py_ssize_t gap = 1; // Default value for gap costs
+    if (gap_obj && (gap = PyLong_AsSsize_t(gap_obj)) && (gap >= 128 || gap <= -128)) {
+        PyErr_Format(PyExc_ValueError, "The `gap_score` must fit into an 8-bit signed integer");
+        return NULL;
+    }
+
+    // Now extract the substitution matrix from the `substitutions_obj`.
+    // It must conform to the buffer protocol, and contain a continuous 256x256 matrix of 8-bit signed integers.
+    sz_error_cost_t const *substitutions;
+
+    // Ensure the substitution matrix object is provided
+    if (!substitutions_obj) {
+        PyErr_Format(PyExc_TypeError, "No substitution matrix provided");
+        return NULL;
+    }
+
+    // Request a buffer view
+    Py_buffer substitutions_view;
+    if (PyObject_GetBuffer(substitutions_obj, &substitutions_view, PyBUF_FULL)) {
+        PyErr_Format(PyExc_TypeError, "Failed to get buffer from substitution matrix");
+        return NULL;
+    }
+
+    // Validate the buffer
+    if (substitutions_view.ndim != 2 || substitutions_view.shape[0] != 256 || substitutions_view.shape[1] != 256 ||
+        substitutions_view.itemsize != sizeof(sz_error_cost_t)) {
+        PyErr_Format(PyExc_ValueError, "Substitution matrix must be a 256x256 matrix of 8-bit signed integers");
+        PyBuffer_Release(&substitutions_view);
+        return NULL;
+    }
+
+    sz_string_view_t str1, str2;
+    if (!export_string_like(str1_obj, &str1.start, &str1.length) ||
+        !export_string_like(str2_obj, &str2.start, &str2.length)) {
+        PyErr_Format(PyExc_TypeError, "Both arguments must be string-like");
         return NULL;
     }
 
-    levenstein_distance_t small_bound = (levenstein_distance_t)bound;
-    levenstein_distance_t distance =
-        sz_levenstein(str1.start, str1.length, str2.start, str2.length, small_bound, temporary_memory.start);
+    // Assign the buffer's data to substitutions
+    substitutions = (sz_error_cost_t const *)substitutions_view.buf;
+
+    // Allocate memory for the Levenshtein matrix
+    sz_memory_allocator_t reusing_allocator;
+    reusing_allocator.allocate = &temporary_memory_allocate;
+    reusing_allocator.free = &temporary_memory_free;
+    reusing_allocator.handle = &temporary_memory;
+
+    sz_ssize_t score = sz_alignment_score(str1.start, str1.length, str2.start, str2.length, substitutions,
+                                          (sz_error_cost_t)gap, &reusing_allocator);
 
-    return PyLong_FromLong(distance);
+    // Don't forget to release the buffer view
+    PyBuffer_Release(&substitutions_view);
+
+    // Check for memory allocation issues
+    if (score == SZ_SSIZE_MAX) {
+        PyErr_NoMemory();
+        return NULL;
+    }
+
+    return PyLong_FromSsize_t(score);
 }
 
 static PyObject *Str_startswith(PyObject *self, PyObject *args, PyObject *kwargs) {
@@ -1183,9 +1319,44 @@ static PyObject *Str_endswith(PyObject *self, PyObject *args, PyObject *kwargs)
     else { Py_RETURN_FALSE; }
 }
 
-static Strs *Str_split_(
-    PyObject *parent, sz_string_view_t text, sz_string_view_t separator, int keepseparator, Py_ssize_t maxsplit) {
+static PyObject *Str_find_first_of(PyObject *self, PyObject *args, PyObject *kwargs) {
+    Py_ssize_t signed_offset;
+    sz_string_view_t text;
+    sz_string_view_t separator;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_find_char_from, &signed_offset, &text, &separator))
+        return NULL;
+    return PyLong_FromSsize_t(signed_offset);
+}
+
+static PyObject *Str_find_first_not_of(PyObject *self, PyObject *args, PyObject *kwargs) {
+    Py_ssize_t signed_offset;
+    sz_string_view_t text;
+    sz_string_view_t separator;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_find_char_not_from, &signed_offset, &text, &separator))
+        return NULL;
+    return PyLong_FromSsize_t(signed_offset);
+}
 
+static PyObject *Str_find_last_of(PyObject *self, PyObject *args, PyObject *kwargs) {
+    Py_ssize_t signed_offset;
+    sz_string_view_t text;
+    sz_string_view_t separator;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_rfind_char_from, &signed_offset, &text, &separator))
+        return NULL;
+    return PyLong_FromSsize_t(signed_offset);
+}
+
+static PyObject *Str_find_last_not_of(PyObject *self, PyObject *args, PyObject *kwargs) {
+    Py_ssize_t signed_offset;
+    sz_string_view_t text;
+    sz_string_view_t separator;
+    if (!_Str_find_implementation_(self, args, kwargs, &sz_rfind_char_not_from, &signed_offset, &text, &separator))
+        return NULL;
+    return PyLong_FromSsize_t(signed_offset);
+}
+
+static Strs *Str_split_(PyObject *parent, sz_string_view_t text, sz_string_view_t separator, int keepseparator,
+                        Py_ssize_t maxsplit) {
     // Create Strs object
     Strs *result = (Strs *)PyObject_New(Strs, &StrsType);
     if (!result) return NULL;
@@ -1213,8 +1384,7 @@ static Strs *Str_split_(
     // Iterate through string, keeping track of the
     sz_size_t last_start = 0;
     while (last_start <= text.length && offsets_count < maxsplit) {
-        sz_string_start_t match =
-            sz_find_substring(text.start + last_start, text.length - last_start, separator.start, separator.length);
+        sz_cptr_t match = sz_find(text.start + last_start, text.length - last_start, separator.start, separator.length);
         sz_size_t offset_in_remaining = match ? match - text.start - last_start : text.length - last_start;
 
         // Reallocate offsets array if needed
@@ -1259,7 +1429,6 @@ static Strs *Str_split_(
 }
 
 static PyObject *Str_split(PyObject *self, PyObject *args, PyObject *kwargs) {
-
     // Check minimum arguments
     int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
     Py_ssize_t nargs = PyTuple_Size(args);
@@ -1334,7 +1503,6 @@ static PyObject *Str_split(PyObject *self, PyObject *args, PyObject *kwargs) {
 }
 
 static PyObject *Str_splitlines(PyObject *self, PyObject *args, PyObject *kwargs) {
-
     // Check minimum arguments
     int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
     Py_ssize_t nargs = PyTuple_Size(args);
@@ -1453,20 +1621,40 @@ static PyNumberMethods Str_as_number = {
     .nb_add = Str_concat,
 };
 
-#define sz_method_flags_m METH_VARARGS | METH_KEYWORDS
+#define SZ_METHOD_FLAGS METH_VARARGS | METH_KEYWORDS
 
 static PyMethodDef Str_methods[] = {
-    //
-    {"find", Str_find, sz_method_flags_m, "Find the first occurrence of a substring."},
-    {"index", Str_index, sz_method_flags_m, "Find the first occurrence of a substring or raise error if missing."},
-    {"contains", Str_contains, sz_method_flags_m, "Check if a string contains a substring."},
-    {"partition", Str_partition, sz_method_flags_m, "Splits string into 3-tuple: before, match, after."},
-    {"count", Str_count, sz_method_flags_m, "Count the occurrences of a substring."},
-    {"split", Str_split, sz_method_flags_m, "Split a string by a separator."},
-    {"splitlines", Str_splitlines, sz_method_flags_m, "Split a string by line breaks."},
-    {"startswith", Str_startswith, sz_method_flags_m, "Check if a string starts with a given prefix."},
-    {"endswith", Str_endswith, sz_method_flags_m, "Check if a string ends with a given suffix."},
-    {"levenstein", Str_levenstein, sz_method_flags_m, "Calculate the Levenshtein distance between two strings."},
+    // Basic `str`-like functionality
+    {"contains", Str_contains, SZ_METHOD_FLAGS, "Check if a string contains a substring."},
+    {"count", Str_count, SZ_METHOD_FLAGS, "Count the occurrences of a substring."},
+    {"splitlines", Str_splitlines, SZ_METHOD_FLAGS, "Split a string by line breaks."},
+    {"startswith", Str_startswith, SZ_METHOD_FLAGS, "Check if a string starts with a given prefix."},
+    {"endswith", Str_endswith, SZ_METHOD_FLAGS, "Check if a string ends with a given suffix."},
+    {"split", Str_split, SZ_METHOD_FLAGS, "Split a string by a separator."},
+
+    // Bidirectional operations
+    {"find", Str_find, SZ_METHOD_FLAGS, "Find the first occurrence of a substring."},
+    {"index", Str_index, SZ_METHOD_FLAGS, "Find the first occurrence of a substring or raise error if missing."},
+    {"partition", Str_partition, SZ_METHOD_FLAGS, "Splits string into 3-tuple: before, first match, after."},
+    {"rfind", Str_rfind, SZ_METHOD_FLAGS, "Find the last occurrence of a substring."},
+    {"rindex", Str_rindex, SZ_METHOD_FLAGS, "Find the last occurrence of a substring or raise error if missing."},
+    {"rpartition", Str_rpartition, SZ_METHOD_FLAGS, "Splits string into 3-tuple: before, last match, after."},
+
+    // Edit distance extensions
+    {"edit_distance", Str_edit_distance, SZ_METHOD_FLAGS, "Calculate the Levenshtein distance between two strings."},
+    {"alignment_score", Str_alignment_score, SZ_METHOD_FLAGS,
+     "Calculate the Needleman-Wunsch alignment score given a substitution cost matrix."},
+
+    // Character search extensions
+    {"find_first_of", Str_find_first_of, SZ_METHOD_FLAGS,
+     "Finds the first occurrence of a character from another string."},
+    {"find_last_of", Str_find_last_of, SZ_METHOD_FLAGS,
+     "Finds the last occurrence of a character from another string."},
+    {"find_first_not_of", Str_find_first_not_of, SZ_METHOD_FLAGS,
+     "Finds the first occurrence of a character not present in another string."},
+    {"find_last_not_of", Str_find_last_not_of, SZ_METHOD_FLAGS,
+     "Finds the last occurrence of a character not present in another string."},
+
     {NULL, NULL, 0, NULL}};
 
 static PyTypeObject StrType = {
@@ -1544,6 +1732,7 @@ static PyObject *Strs_shuffle(Strs *self, PyObject *args, PyObject *kwargs) {
     size_t count = reordered->count;
 
     // Fisher-Yates Shuffle Algorithm
+    srand(seed);
     for (size_t i = count - 1; i > 0; --i) {
         size_t j = rand() % (i + 1);
         // Swap parts[i] and parts[j]
@@ -1555,11 +1744,8 @@ static PyObject *Strs_shuffle(Strs *self, PyObject *args, PyObject *kwargs) {
     Py_RETURN_NONE;
 }
 
-static sz_bool_t Strs_sort_(Strs *self,
-                            sz_string_view_t **parts_output,
-                            sz_size_t **order_output,
+static sz_bool_t Strs_sort_(Strs *self, sz_string_view_t **parts_output, sz_size_t **order_output,
                             sz_size_t *count_output) {
-
     // Change the layout
     if (!prepare_strings_for_reordering(self)) {
         PyErr_Format(PyExc_TypeError, "Failed to prepare the sequence for sorting");
@@ -1584,16 +1770,14 @@ static sz_bool_t Strs_sort_(Strs *self,
 
     // Call our sorting algorithm
     sz_sequence_t sequence;
-    sz_sort_config_t sort_config;
     memset(&sequence, 0, sizeof(sequence));
-    memset(&sort_config, 0, sizeof(sort_config));
     sequence.order = (sz_size_t *)temporary_memory.start;
     sequence.count = count;
     sequence.handle = parts;
     sequence.get_start = parts_get_start;
     sequence.get_length = parts_get_length;
     for (sz_size_t i = 0; i != sequence.count; ++i) sequence.order[i] = i;
-    sz_sort(&sequence, &sort_config);
+    sz_sort(&sequence);
 
     // Export results
     *parts_output = parts;
@@ -1729,9 +1913,9 @@ static PyMappingMethods Strs_as_mapping = {
 };
 
 static PyMethodDef Strs_methods[] = {
-    {"shuffle", Strs_shuffle, sz_method_flags_m, "Shuffle the elements of the Strs object."},  //
-    {"sort", Strs_sort, sz_method_flags_m, "Sort the elements of the Strs object."},           //
-    {"order", Strs_order, sz_method_flags_m, "Provides the indexes to achieve sorted order."}, //
+    {"shuffle", Strs_shuffle, SZ_METHOD_FLAGS, "Shuffle the elements of the Strs object."},  //
+    {"sort", Strs_sort, SZ_METHOD_FLAGS, "Sort the elements of the Strs object."},           //
+    {"order", Strs_order, SZ_METHOD_FLAGS, "Provides the indexes to achieve sorted order."}, //
     {NULL, NULL, 0, NULL}};
 
 static PyTypeObject StrsType = {
@@ -1755,22 +1939,46 @@ static void stringzilla_cleanup(PyObject *m) {
 }
 
 static PyMethodDef stringzilla_methods[] = {
-    {"find", Str_find, sz_method_flags_m, "Find the first occurrence of a substring."},
-    {"index", Str_index, sz_method_flags_m, "Find the first occurrence of a substring or raise error if missing."},
-    {"contains", Str_contains, sz_method_flags_m, "Check if a string contains a substring."},
-    {"partition", Str_partition, sz_method_flags_m, "Splits string into 3-tuple: before, match, after."},
-    {"count", Str_count, sz_method_flags_m, "Count the occurrences of a substring."},
-    {"split", Str_split, sz_method_flags_m, "Split a string by a separator."},
-    {"splitlines", Str_splitlines, sz_method_flags_m, "Split a string by line breaks."},
-    {"startswith", Str_startswith, sz_method_flags_m, "Check if a string starts with a given prefix."},
-    {"endswith", Str_endswith, sz_method_flags_m, "Check if a string ends with a given suffix."},
-    {"levenstein", Str_levenstein, sz_method_flags_m, "Calculate the Levenshtein distance between two strings."},
+    // Basic `str`-like functionality
+    {"contains", Str_contains, SZ_METHOD_FLAGS, "Check if a string contains a substring."},
+    {"count", Str_count, SZ_METHOD_FLAGS, "Count the occurrences of a substring."},
+    {"splitlines", Str_splitlines, SZ_METHOD_FLAGS, "Split a string by line breaks."},
+    {"startswith", Str_startswith, SZ_METHOD_FLAGS, "Check if a string starts with a given prefix."},
+    {"endswith", Str_endswith, SZ_METHOD_FLAGS, "Check if a string ends with a given suffix."},
+    {"split", Str_split, SZ_METHOD_FLAGS, "Split a string by a separator."},
+
+    // Bidirectional operations
+    {"find", Str_find, SZ_METHOD_FLAGS, "Find the first occurrence of a substring."},
+    {"index", Str_index, SZ_METHOD_FLAGS, "Find the first occurrence of a substring or raise error if missing."},
+    {"partition", Str_partition, SZ_METHOD_FLAGS, "Splits string into 3-tuple: before, first match, after."},
+    {"rfind", Str_rfind, SZ_METHOD_FLAGS, "Find the last occurrence of a substring."},
+    {"rindex", Str_rindex, SZ_METHOD_FLAGS, "Find the last occurrence of a substring or raise error if missing."},
+    {"rpartition", Str_rpartition, SZ_METHOD_FLAGS, "Splits string into 3-tuple: before, last match, after."},
+
+    // Edit distance extensions
+    {"edit_distance", Str_edit_distance, SZ_METHOD_FLAGS, "Calculate the Levenshtein distance between two strings."},
+    {"alignment_score", Str_alignment_score, SZ_METHOD_FLAGS,
+     "Calculate the Needleman-Wunsch alignment score given a substitution cost matrix."},
+
+    // Character search extensions
+    {"find_first_of", Str_find_first_of, SZ_METHOD_FLAGS,
+     "Finds the first occurrence of a character from another string."},
+    {"find_last_of", Str_find_last_of, SZ_METHOD_FLAGS,
+     "Finds the last occurrence of a character from another string."},
+    {"find_first_not_of", Str_find_first_not_of, SZ_METHOD_FLAGS,
+     "Finds the first occurrence of a character not present in another string."},
+    {"find_last_not_of", Str_find_last_not_of, SZ_METHOD_FLAGS,
+     "Finds the last occurrence of a character not present in another string."},
+
+    // Global unary extensions
+    {"hash", Str_like_hash, SZ_METHOD_FLAGS, "Hash a string or a byte-array."},
+
     {NULL, NULL, 0, NULL}};
 
 static PyModuleDef stringzilla_module = {
     PyModuleDef_HEAD_INIT,
     "stringzilla",
-    "Crunch 100+ GB Strings in Python with ease",
+    "SIMD-accelerated string search, sort, hashes, fingerprints, & edit distances",
     -1,
     stringzilla_methods,
     NULL,
@@ -1791,6 +1999,31 @@ PyMODINIT_FUNC PyInit_stringzilla(void) {
     m = PyModule_Create(&stringzilla_module);
     if (m == NULL) return NULL;
 
+    // Add version metadata
+    {
+        char version_str[50];
+        sprintf(version_str, "%d.%d.%d", STRINGZILLA_VERSION_MAJOR, STRINGZILLA_VERSION_MINOR,
+                STRINGZILLA_VERSION_PATCH);
+        PyModule_AddStringConstant(m, "__version__", version_str);
+    }
+
+    // Define SIMD capabilities
+    {
+        sz_capability_t caps = sz_capabilities();
+        char caps_str[512];
+        char const *serial = (caps & sz_cap_serial_k) ? "serial," : "";
+        char const *neon = (caps & sz_cap_arm_neon_k) ? "neon," : "";
+        char const *sve = (caps & sz_cap_arm_sve_k) ? "sve," : "";
+        char const *avx2 = (caps & sz_cap_x86_avx2_k) ? "avx2," : "";
+        char const *avx512f = (caps & sz_cap_x86_avx512f_k) ? "avx512f," : "";
+        char const *avx512vl = (caps & sz_cap_x86_avx512vl_k) ? "avx512vl," : "";
+        char const *avx512bw = (caps & sz_cap_x86_avx512bw_k) ? "avx512bw," : "";
+        char const *avx512vbmi = (caps & sz_cap_x86_avx512vbmi_k) ? "avx512vbmi," : "";
+        char const *gfni = (caps & sz_cap_x86_gfni_k) ? "gfni," : "";
+        sprintf(caps_str, "%s%s%s%s%s%s%s%s%s", serial, neon, sve, avx2, avx512f, avx512vl, avx512bw, avx512vbmi, gfni);
+        PyModule_AddStringConstant(m, "__capabilities__", caps_str);
+    }
+
     Py_INCREF(&StrType);
     if (PyModule_AddObject(m, "Str", (PyObject *)&StrType) < 0) {
         Py_XDECREF(&StrType);
diff --git a/rust/lib.rs b/rust/lib.rs
new file mode 100644
index 00000000..a8cc48ee
--- /dev/null
+++ b/rust/lib.rs
@@ -0,0 +1,247 @@
+#![cfg_attr(not(test), no_std)]
+
+use core::ffi::c_void;
+
+// Import the functions from the StringZilla C library.
+extern "C" {
+    fn sz_find(
+        haystack: *mut c_void,
+        haystack_length: usize,
+        needle: *const c_void,
+        needle_length: usize,
+    ) -> *mut c_void;
+
+    fn sz_rfind(
+        haystack: *mut c_void,
+        haystack_length: usize,
+        needle: *const c_void,
+        needle_length: usize,
+    ) -> *mut c_void;
+
+    fn sz_find_char_from(
+        haystack: *mut c_void,
+        haystack_length: usize,
+        needle: *const c_void,
+        needle_length: usize,
+    ) -> *mut c_void;
+
+    fn sz_rfind_char_from(
+        haystack: *mut c_void,
+        haystack_length: usize,
+        needle: *const c_void,
+        needle_length: usize,
+    ) -> *mut c_void;
+
+    fn sz_find_char_not_from(
+        haystack: *mut c_void,
+        haystack_length: usize,
+        needle: *const c_void,
+        needle_length: usize,
+    ) -> *mut c_void;
+
+    fn sz_rfind_char_not_from(
+        haystack: *mut c_void,
+        haystack_length: usize,
+        needle: *const c_void,
+        needle_length: usize,
+    ) -> *mut c_void;
+}
+
+pub trait StringZilla<N>
+where
+    N: AsRef<[u8]>,
+{
+    /// Generic function to find the first occurrence of a substring or a subarray.
+    fn sz_find(&self, needle: N) -> Option<usize>;
+    /// Generic function to find the last occurrence of a substring or a subarray.
+    fn sz_rfind(&self, needle: N) -> Option<usize>;
+    /// Generic function to find the first occurrence of a character/element from the second argument.
+    fn sz_find_char_from(&self, needles: N) -> Option<usize>;
+    /// Generic function to find the last occurrence of a character/element from the second argument.
+    fn sz_rfind_char_from(&self, needles: N) -> Option<usize>;
+    /// Generic function to find the first occurrence of a character/element from the second argument.
+    fn sz_find_char_not_from(&self, needles: N) -> Option<usize>;
+    /// Generic function to find the last occurrence of a character/element from the second argument.
+    fn sz_rfind_char_not_from(&self, needles: N) -> Option<usize>;
+}
+
+impl<T, N> StringZilla<N> for T
+where
+    T: AsRef<[u8]>,
+    N: AsRef<[u8]>,
+{
+    fn sz_find(&self, needle: N) -> Option<usize> {
+        let haystack_ref = self.as_ref();
+        let needle_ref = needle.as_ref();
+        let haystack_pointer = haystack_ref.as_ptr() as *mut c_void;
+        let haystack_length = haystack_ref.len();
+        let needle_pointer = needle_ref.as_ptr() as *const c_void;
+        let needle_length = needle_ref.len();
+        let result = unsafe {
+            sz_find(
+                haystack_pointer,
+                haystack_length,
+                needle_pointer,
+                needle_length,
+            )
+        };
+
+        if result.is_null() {
+            None
+        } else {
+            Some(unsafe { result.offset_from(haystack_pointer) } as usize)
+        }
+    }
+
+    fn sz_rfind(&self, needle: N) -> Option<usize> {
+        let haystack_ref = self.as_ref();
+        let needle_ref = needle.as_ref();
+        let haystack_pointer = haystack_ref.as_ptr() as *mut c_void;
+        let haystack_length = haystack_ref.len();
+        let needle_pointer = needle_ref.as_ptr() as *const c_void;
+        let needle_length = needle_ref.len();
+        let result = unsafe {
+            sz_rfind(
+                haystack_pointer,
+                haystack_length,
+                needle_pointer,
+                needle_length,
+            )
+        };
+
+        if result.is_null() {
+            None
+        } else {
+            Some(unsafe { result.offset_from(haystack_pointer) } as usize)
+        }
+    }
+
+    fn sz_find_char_from(&self, needles: N) -> Option<usize> {
+        let haystack_ref = self.as_ref();
+        let needles_ref = needles.as_ref();
+        let haystack_pointer = haystack_ref.as_ptr() as *mut c_void;
+        let haystack_length = haystack_ref.len();
+        let needles_pointer = needles_ref.as_ptr() as *const c_void;
+        let needles_length = needles_ref.len();
+        let result = unsafe {
+            sz_find_char_from(
+                haystack_pointer,
+                haystack_length,
+                needles_pointer,
+                needles_length,
+            )
+        };
+        if result.is_null() {
+            None
+        } else {
+            Some(unsafe { result.offset_from(haystack_pointer) } as usize)
+        }
+    }
+
+    fn sz_rfind_char_from(&self, needles: N) -> Option<usize> {
+        let haystack_ref = self.as_ref();
+        let needles_ref = needles.as_ref();
+        let haystack_pointer = haystack_ref.as_ptr() as *mut c_void;
+        let haystack_length = haystack_ref.len();
+        let needles_pointer = needles_ref.as_ptr() as *const c_void;
+        let needles_length = needles_ref.len();
+        let result = unsafe {
+            sz_rfind_char_from(
+                haystack_pointer,
+                haystack_length,
+                needles_pointer,
+                needles_length,
+            )
+        };
+        if result.is_null() {
+            None
+        } else {
+            Some(unsafe { result.offset_from(haystack_pointer) } as usize)
+        }
+    }
+
+    fn sz_find_char_not_from(&self, needles: N) -> Option<usize> {
+        let haystack_ref = self.as_ref();
+        let needles_ref = needles.as_ref();
+        let haystack_pointer = haystack_ref.as_ptr() as *mut c_void;
+        let haystack_length = haystack_ref.len();
+        let needles_pointer = needles_ref.as_ptr() as *const c_void;
+        let needles_length = needles_ref.len();
+        let result = unsafe {
+            sz_find_char_not_from(
+                haystack_pointer,
+                haystack_length,
+                needles_pointer,
+                needles_length,
+            )
+        };
+        if result.is_null() {
+            None
+        } else {
+            Some(unsafe { result.offset_from(haystack_pointer) } as usize)
+        }
+    }
+
+    fn sz_rfind_char_not_from(&self, needles: N) -> Option<usize> {
+        let haystack_ref = self.as_ref();
+        let needles_ref = needles.as_ref();
+        let haystack_pointer = haystack_ref.as_ptr() as *mut c_void;
+        let haystack_length = haystack_ref.len();
+        let needles_pointer = needles_ref.as_ptr() as *const c_void;
+        let needles_length = needles_ref.len();
+        let result = unsafe {
+            sz_rfind_char_not_from(
+                haystack_pointer,
+                haystack_length,
+                needles_pointer,
+                needles_length,
+            )
+        };
+        if result.is_null() {
+            None
+        } else {
+            Some(unsafe { result.offset_from(haystack_pointer) } as usize)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::borrow::Cow;
+
+    use crate::StringZilla;
+
+    #[test]
+    fn basics() {
+        let my_string: String = String::from("Hello, world!");
+        let my_str: &str = my_string.as_str();
+        let my_cow_str: Cow<'_, str> = Cow::from(&my_string);
+
+        // Use the generic function with a String
+        assert_eq!(my_string.sz_find("world"), Some(7));
+        assert_eq!(my_string.sz_rfind("world"), Some(7));
+        assert_eq!(my_string.sz_find_char_from("world"), Some(2));
+        assert_eq!(my_string.sz_rfind_char_from("world"), Some(11));
+        assert_eq!(my_string.sz_find_char_not_from("world"), Some(0));
+        assert_eq!(my_string.sz_rfind_char_not_from("world"), Some(12));
+
+        // Use the generic function with a &str
+        assert_eq!(my_str.sz_find("world"), Some(7));
+        assert_eq!(my_str.sz_find("world"), Some(7));
+        assert_eq!(my_str.sz_find_char_from("world"), Some(2));
+        assert_eq!(my_str.sz_rfind_char_from("world"), Some(11));
+        assert_eq!(my_str.sz_find_char_not_from("world"), Some(0));
+        assert_eq!(my_str.sz_rfind_char_not_from("world"), Some(12));
+
+        // Use the generic function with a Cow<'_, str>
+        assert_eq!(my_cow_str.as_ref().sz_find("world"), Some(7));
+        assert_eq!(my_cow_str.as_ref().sz_find("world"), Some(7));
+        assert_eq!(my_cow_str.as_ref().sz_find_char_from("world"), Some(2));
+        assert_eq!(my_cow_str.as_ref().sz_rfind_char_from("world"), Some(11));
+        assert_eq!(my_cow_str.as_ref().sz_find_char_not_from("world"), Some(0));
+        assert_eq!(
+            my_cow_str.as_ref().sz_rfind_char_not_from("world"),
+            Some(12)
+        );
+    }
+}
diff --git a/scripts/bench.hpp b/scripts/bench.hpp
new file mode 100644
index 00000000..bd68d99a
--- /dev/null
+++ b/scripts/bench.hpp
@@ -0,0 +1,367 @@
+/**
+ *  @brief  Helper structures and functions for C++ benchmarks.
+ */
+#pragma once
+#include <algorithm>
+#include <chrono>     // `std::chrono::high_resolution_clock`
+#include <clocale>    // `std::setlocale`
+#include <cstring>    // `std::memcpy`
+#include <functional> // `std::equal_to`
+#include <limits>     // `std::numeric_limits`
+#include <random>     // `std::random_device`, `std::mt19937`
+#include <string>     // `std::hash`
+#include <vector>
+
+#include <string_view> // Requires C++17
+
+#include <stringzilla/stringzilla.h>
+#include <stringzilla/stringzilla.hpp>
+
+#include "test.hpp" // `read_file`
+
+#if SZ_DEBUG // Make debugging faster
+#define default_seconds_m 10
+#else
+#define default_seconds_m 30
+#endif
+
+namespace sz = ashvardanian::stringzilla;
+
+namespace ashvardanian {
+namespace stringzilla {
+namespace scripts {
+
+using seconds_t = double;
+
+struct benchmark_result_t {
+    std::size_t iterations = 0;
+    std::size_t bytes_passed = 0;
+    seconds_t seconds = 0;
+};
+
+using unary_function_t = std::function<std::size_t(std::string_view)>;
+using binary_function_t = std::function<std::size_t(std::string_view, std::string_view)>;
+
+/**
+ *  @brief  Wrapper for a single execution backend.
+ */
+template <typename function_type>
+struct tracked_function_gt {
+    std::string name {""};
+    function_type function {nullptr};
+    bool needs_testing {false};
+
+    std::size_t failed_count;
+    std::vector<std::string> failed_strings;
+    benchmark_result_t results;
+
+    tracked_function_gt(std::string name = "", function_type function = nullptr, bool needs_testing = false)
+        : name(name), function(function), needs_testing(needs_testing), failed_count(0), failed_strings(), results() {}
+
+    tracked_function_gt(tracked_function_gt const &) = default;
+    tracked_function_gt &operator=(tracked_function_gt const &) = default;
+
+    void print() const {
+        bool is_binary = std::is_same<function_type, binary_function_t>();
+
+        // If failures have occured, output them to file tos implify the debugging process.
+        bool contains_failures = !failed_strings.empty();
+        if (contains_failures) {
+            // The file name is made of the string hash and the function name.
+            for (std::size_t fail_index = 0; fail_index != failed_strings.size();) {
+                std::string const &first_argument = failed_strings[fail_index];
+                std::string file_name =
+                    "failed_" + name + "_" + std::to_string(std::hash<std::string> {}(first_argument));
+                if (is_binary) {
+                    std::string const &second_argument = failed_strings[fail_index + 1];
+                    write_file(file_name + ".first.txt", first_argument);
+                    write_file(file_name + ".second.txt", second_argument);
+                    fail_index += 2;
+                }
+                else {
+                    write_file(file_name + ".txt", first_argument);
+                    fail_index += 1;
+                }
+            }
+        }
+
+        // Now let's print in the format:
+        //  - name, up to 20 characters
+        //  - throughput in GB/s with up to 3 significant digits, 10 characters
+        //  - call latency in ns with up to 1 significant digit, 10 characters
+        //  - number of failed tests, 10 characters
+        //  - first example of a failed test, up to 20 characters
+        char const *format;
+        if (is_binary) { format = "- %-20s %15.4f GB/s %15.1f ns %10zu errors in %10zu iterations %-20s %-20s\n"; }
+        else { format = "- %-20s %15.4f GB/s %15.1f ns %10zu errors in %10zu iterations %-20s\n"; }
+
+        std::printf(format, name.c_str(), results.bytes_passed / results.seconds / 1.e9,
+                    results.seconds * 1e9 / results.iterations, failed_count, results.iterations,
+                    failed_strings.size() ? failed_strings[0].c_str() : "",
+                    failed_strings.size() >= 2 && is_binary ? failed_strings[1].c_str() : "");
+    }
+};
+
+using tracked_unary_functions_t = std::vector<tracked_function_gt<unary_function_t>>;
+using tracked_binary_functions_t = std::vector<tracked_function_gt<binary_function_t>>;
+
+/**
+ *  @brief  Stops compilers from optimizing out the expression.
+ *          Shamelessly stolen from Google Benchmark.
+ */
+template <typename argument_type>
+inline void do_not_optimize(argument_type &&value) {
+#if defined(_MSC_VER) // MSVC
+    using plain_type = typename std::remove_reference<argument_type>::type;
+    // Use the `volatile` keyword and a memory barrier to prevent optimization
+    volatile plain_type *p = &value;
+    _ReadWriteBarrier();
+#else // Other compilers (GCC, Clang, etc.)
+    asm volatile("" : "+r,m"(value) : : "memory");
+#endif
+}
+
+/**
+ *  @brief  Rounds the number down to the preceding power of two.
+ *          Equivalent to `std::bit_ceil`.
+ */
+inline std::size_t bit_floor(std::size_t n) {
+    if (n == 0) return 0;
+    std::size_t most_siginificant_bit_position = 0;
+    while (n > 1) n >>= 1, most_siginificant_bit_position++;
+    return static_cast<std::size_t>(1) << most_siginificant_bit_position;
+}
+
+template <typename is_separator_callback_type>
+inline std::vector<std::string_view> tokenize(std::string_view str, is_separator_callback_type &&is_separator) {
+    std::vector<std::string_view> words;
+    std::size_t start = 0;
+    for (std::size_t end = 0; end <= str.length(); ++end) {
+        if (end == str.length() || is_separator(str[end])) {
+            if (start < end) words.push_back({&str[start], end - start});
+            start = end + 1;
+        }
+    }
+    return words;
+}
+
+/**
+ *  @brief  Splits a string into words, using newlines, tabs, and whitespaces as delimiters.
+ */
+inline std::vector<std::string_view> tokenize(std::string_view str) {
+    return tokenize(str, [](char c) { return std::isspace(c); });
+}
+
+template <typename result_string_type = std::string_view, typename from_string_type = result_string_type,
+          typename comparator_type = std::equal_to<std::size_t>>
+inline std::vector<result_string_type> filter_by_length(std::vector<from_string_type> tokens, std::size_t n,
+                                                        comparator_type &&comparator = {}) {
+    std::vector<result_string_type> result;
+    for (auto const &str : tokens)
+        if (comparator(str.length(), n)) result.push_back({str.data(), str.length()});
+    return result;
+}
+
+struct dataset_t {
+    std::string text;
+    std::vector<std::string_view> tokens;
+    std::vector<std::string_view> lines;
+};
+
+/**
+ *  @brief  Loads a dataset from a file.
+ */
+inline dataset_t make_dataset_from_path(std::string path) {
+    dataset_t data;
+    data.text = read_file(path);
+    data.text.resize(bit_floor(data.text.size()));
+    data.tokens = tokenize(data.text);
+    data.tokens.resize(bit_floor(data.tokens.size()));
+    data.lines = tokenize(data.text, [](char c) { return c == '\n'; });
+    data.lines.resize(bit_floor(data.lines.size()));
+
+#ifdef NDEBUG // Shuffle only in release mode
+    std::random_device random_device;
+    std::mt19937 random_generator(random_device());
+    std::shuffle(data.tokens.begin(), data.tokens.end(), random_generator);
+    std::shuffle(data.lines.begin(), data.lines.end(), random_generator);
+#endif
+
+    // Report some basic stats about the dataset
+    double mean_token_bytes = 0, mean_line_bytes = 0;
+    for (auto const &str : data.tokens) mean_token_bytes += str.size();
+    for (auto const &str : data.lines) mean_line_bytes += str.size();
+    mean_token_bytes /= data.tokens.size();
+    mean_line_bytes /= data.lines.size();
+
+    std::setlocale(LC_NUMERIC, "");
+    std::printf(                                     //
+        "Parsed the dataset with:\n"                 //
+        "- %zu words of mean length ~ %.2f bytes\n"  //
+        "- %zu lines of mean length ~ %.2f bytes\n", //
+        data.tokens.size(), mean_token_bytes, data.lines.size(), mean_line_bytes);
+
+    return data;
+}
+
+/**
+ *  @brief  Loads a dataset, depending on the passed CLI arguments.
+ */
+inline dataset_t make_dataset(int argc, char const *argv[]) {
+    if (argc != 2) { throw std::runtime_error("Usage: " + std::string(argv[0]) + " <path>"); }
+    return make_dataset_from_path(argv[1]);
+}
+
+inline sz_string_view_t to_c(std::string_view str) noexcept { return {str.data(), str.size()}; }
+inline sz_string_view_t to_c(std::string const &str) noexcept { return {str.data(), str.size()}; }
+inline sz_string_view_t to_c(sz::string_view str) noexcept { return {str.data(), str.size()}; }
+inline sz_string_view_t to_c(sz::string const &str) noexcept { return {str.data(), str.size()}; }
+inline sz_string_view_t to_c(sz_string_view_t str) noexcept { return str; }
+
+/**
+ *  @brief  Loop over all elements in a dataset in somewhat random order, benchmarking the function cost.
+ *  @param  strings Strings to loop over. Length must be a power of two.
+ *  @param  function Function to be applied to each `sz_string_view_t`. Must return the number of bytes processed.
+ *  @return Number of seconds per iteration.
+ */
+template <typename strings_type, typename function_type>
+benchmark_result_t bench_on_tokens(strings_type &&strings, function_type &&function,
+                                   seconds_t max_time = default_seconds_m) {
+
+    namespace stdc = std::chrono;
+    using stdcc = stdc::high_resolution_clock;
+    stdcc::time_point t1 = stdcc::now();
+    benchmark_result_t result;
+    std::size_t lookup_mask = bit_floor(strings.size()) - 1;
+
+    while (true) {
+        // Unroll a few iterations, to avoid some for-loops overhead and minimize impact of time-tracking
+        {
+            result.bytes_passed += function(strings[(result.iterations + 0) & lookup_mask]) +
+                                   function(strings[(result.iterations + 1) & lookup_mask]) +
+                                   function(strings[(result.iterations + 2) & lookup_mask]) +
+                                   function(strings[(result.iterations + 3) & lookup_mask]);
+            result.iterations += 4;
+        }
+
+        stdcc::time_point t2 = stdcc::now();
+        result.seconds = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count() / 1.e9;
+        if (result.seconds > max_time) break;
+    }
+
+    return result;
+}
+
+/**
+ *  @brief  Loop over all elements in a dataset, benchmarking the function cost.
+ *  @param  strings Strings to loop over. Length must be a power of two.
+ *  @param  function Function to be applied to pairs of `sz_string_view_t`.
+ *                   Must return the number of bytes processed.
+ *  @return Number of seconds per iteration.
+ */
+template <typename strings_type, typename function_type>
+benchmark_result_t bench_on_token_pairs(strings_type &&strings, function_type &&function,
+                                        seconds_t max_time = default_seconds_m) {
+
+    namespace stdc = std::chrono;
+    using stdcc = stdc::high_resolution_clock;
+    stdcc::time_point t1 = stdcc::now();
+    benchmark_result_t result;
+    std::size_t lookup_mask = bit_floor(strings.size()) - 1;
+    std::size_t largest_prime = 18446744073709551557ull;
+
+    while (true) {
+        // Unroll a few iterations, to avoid some for-loops overhead and minimize impact of time-tracking
+        {
+            auto second = (result.iterations * largest_prime) & lookup_mask;
+            result.bytes_passed += function(strings[(result.iterations + 0) & lookup_mask], strings[second]) +
+                                   function(strings[(result.iterations + 1) & lookup_mask], strings[second]) +
+                                   function(strings[(result.iterations + 2) & lookup_mask], strings[second]) +
+                                   function(strings[(result.iterations + 3) & lookup_mask], strings[second]);
+            result.iterations += 4;
+        }
+
+        stdcc::time_point t2 = stdcc::now();
+        result.seconds = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count() / 1.e9;
+        if (result.seconds > max_time) break;
+    }
+
+    return result;
+}
+
+/**
+ *  @brief  Evaluation for unary string operations: hashing.
+ */
+template <typename strings_type, typename functions_type>
+void bench_unary_functions(strings_type &&strings, functions_type &&variants) {
+
+    for (std::size_t variant_idx = 0; variant_idx != variants.size(); ++variant_idx) {
+        auto &variant = variants[variant_idx];
+
+        // Tests
+        if (variant.function && variant.needs_testing) {
+            bench_on_tokens(strings, [&](auto str) -> std::size_t {
+                auto baseline = variants[0].function(str);
+                auto result = variant.function(str);
+                if (result != baseline) {
+                    ++variant.failed_count;
+                    if (variant.failed_strings.empty()) {
+                        variant.failed_strings.push_back({to_c(str).start, to_c(str).length});
+                    }
+                }
+                return to_c(str).length;
+            });
+        }
+
+        // Benchmarks
+        if (variant.function) {
+            variant.results = bench_on_tokens(strings, [&](auto str) -> std::size_t {
+                do_not_optimize(variant.function(str));
+                return to_c(str).length;
+            });
+        }
+
+        variant.print();
+    }
+}
+
+/**
+ *  @brief  Evaluation for binary string operations: equality, ordering, prefix, suffix, distance.
+ */
+template <typename strings_type, typename functions_type>
+void bench_binary_functions(strings_type &&strings, functions_type &&variants) {
+
+    for (std::size_t variant_idx = 0; variant_idx != variants.size(); ++variant_idx) {
+        auto &variant = variants[variant_idx];
+
+        // Tests
+        if (variant.function && variant.needs_testing) {
+            bench_on_token_pairs(strings, [&](auto str_a, auto str_b) -> std::size_t {
+                auto baseline = variants[0].function(str_a, str_b);
+                auto result = variant.function(str_a, str_b);
+                if (result != baseline) {
+                    ++variant.failed_count;
+                    if (variant.failed_strings.empty()) {
+                        variant.failed_strings.push_back({to_c(str_a).start, to_c(str_a).length});
+                        variant.failed_strings.push_back({to_c(str_b).start, to_c(str_b).length});
+                    }
+                }
+                return to_c(str_a).length + to_c(str_b).length;
+            });
+        }
+
+        // Benchmarks
+        if (variant.function) {
+            variant.results = bench_on_token_pairs(strings, [&](auto str_a, auto str_b) -> std::size_t {
+                do_not_optimize(variant.function(str_a, str_b));
+                return to_c(str_a).length + to_c(str_b).length;
+            });
+        }
+
+        variant.print();
+    }
+}
+
+} // namespace scripts
+} // namespace stringzilla
+} // namespace ashvardanian
\ No newline at end of file
diff --git a/scripts/bench.ipynb b/scripts/bench.ipynb
index 52ae5e56..533be945 100644
--- a/scripts/bench.ipynb
+++ b/scripts/bench.ipynb
@@ -2,7 +2,24 @@
  "cells": [
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "File ‘../leipzig1M.txt’ already there; not retrieving.\n"
+     ]
+    }
+   ],
+   "source": [
+    "!wget --no-clobber -O ../leipzig1M.txt https://introcs.cs.princeton.edu/python/42sort/leipzig1m.txt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -11,7 +28,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -22,18 +39,37 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 3,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "129,644,797, 129,644,797\n"
+     ]
+    }
+   ],
    "source": [
     "print(f\"{len(pythonic_str):,}, {len(sz_str):,}\")"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 7,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(1000000, 1000000)"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
    "source": [
     "pythonic_str.count(\"\\n\"), sz_str.count(\"\\n\")"
    ]
@@ -47,9 +83,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 8,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "455 ms ± 23.6 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 10\n",
     "sorted(pythonic_str.splitlines())"
@@ -57,19 +101,35 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 10,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "455 ms ± 17.1 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 10\n",
-    "sz_str.splitlines().sorted()"
+    "sz_str.splitlines().sort()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 11,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "132 ms ± 13 ms per loop (mean ± std. dev. of 100 runs, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 100\n",
     "pythonic_str.count(pattern)"
@@ -77,9 +137,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 12,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "33.1 ms ± 7.74 ms per loop (mean ± std. dev. of 100 runs, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 100\n",
     "sz_str.count(pattern)"
@@ -94,9 +162,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 13,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "30.1 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "hash(pythonic_str)"
@@ -104,9 +180,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 14,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "21.5 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "hash(sz_str)"
@@ -114,9 +198,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 15,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1.23 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "pythonic_str.find(\" \")"
@@ -124,9 +216,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 16,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "3.4 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "sz_str.find(\" \")"
@@ -134,9 +234,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 17,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "87.3 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "pythonic_str.partition(\" \")"
@@ -144,9 +252,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 18,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "18.3 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "sz_str.partition(\" \")"
@@ -161,9 +277,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 19,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "10.7 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "pythonic_str.split(\" \").sort()"
@@ -171,13 +295,203 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 20,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "9.19 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
    "source": [
     "%%timeit -n 1 -r 1\n",
     "sz_str.split(\" \").sort()"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Edit Distance"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Requirement already satisfied: python-Levenshtein in /home/av/miniconda3/lib/python3.11/site-packages (0.23.0)\n",
+      "Requirement already satisfied: Levenshtein==0.23.0 in /home/av/miniconda3/lib/python3.11/site-packages (from python-Levenshtein) (0.23.0)\n",
+      "Requirement already satisfied: rapidfuzz<4.0.0,>=3.1.0 in /home/av/miniconda3/lib/python3.11/site-packages (from Levenshtein==0.23.0->python-Levenshtein) (3.5.2)\n",
+      "Requirement already satisfied: levenshtein in /home/av/miniconda3/lib/python3.11/site-packages (0.23.0)\n",
+      "Requirement already satisfied: rapidfuzz<4.0.0,>=3.1.0 in /home/av/miniconda3/lib/python3.11/site-packages (from levenshtein) (3.5.2)\n",
+      "Requirement already satisfied: jellyfish in /home/av/miniconda3/lib/python3.11/site-packages (1.0.3)\n",
+      "Requirement already satisfied: editdistance in /home/av/miniconda3/lib/python3.11/site-packages (0.6.2)\n",
+      "Collecting distance\n",
+      "  Downloading Distance-0.1.3.tar.gz (180 kB)\n",
+      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m180.3/180.3 kB\u001b[0m \u001b[31m5.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[?25h  Preparing metadata (setup.py) ... \u001b[?25ldone\n",
+      "\u001b[?25hBuilding wheels for collected packages: distance\n",
+      "  Building wheel for distance (setup.py) ... \u001b[?25ldone\n",
+      "\u001b[?25h  Created wheel for distance: filename=Distance-0.1.3-py3-none-any.whl size=16258 sha256=b688ad5c13aada5f4d13ee0844df820e9f6260d94ac0456de71a70d11872ebf4\n",
+      "  Stored in directory: /home/av/.cache/pip/wheels/fb/cd/9c/3ab5d666e3bcacc58900b10959edd3816cc9557c7337986322\n",
+      "Successfully built distance\n",
+      "Installing collected packages: distance\n",
+      "Successfully installed distance-0.1.3\n",
+      "Requirement already satisfied: polyleven in /home/av/miniconda3/lib/python3.11/site-packages (0.8)\n"
+     ]
+    }
+   ],
+   "source": [
+    "!pip install python-Levenshtein  # 4.8 M/mo: https://github.com/maxbachmann/python-Levenshtein\n",
+    "!pip install levenshtein # 4.2 M/mo: https://github.com/maxbachmann/Levenshtein\n",
+    "!pip install jellyfish # 2.3 M/mo: https://github.com/jamesturk/jellyfish/\n",
+    "!pip install editdistance # 700 k/mo: https://github.com/roy-ht/editdistance\n",
+    "!pip install distance # 160 k/mo: https://github.com/doukremt/distance\n",
+    "!pip install polyleven # 34 k/mo: https://github.com/fujimotos/polyleven"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "words = pythonic_str.split(\" \")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "4.5 s ± 55.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+     ]
+    },
+    {
+     "ename": "",
+     "evalue": "",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31mThe Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details."
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "for word in words:\n",
+    "    sz.levenshtein(word, \"rebel\")\n",
+    "    sz.levenshtein(word, \"statement\")\n",
+    "    sz.levenshtein(word, \"sent\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import polyleven as pl"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "4.49 s ± 105 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "for word in words:\n",
+    "    pl.levenshtein(word, \"rebel\", 100)\n",
+    "    pl.levenshtein(word, \"statement\", 100)\n",
+    "    pl.levenshtein(word, \"sent\", 100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import editdistance as ed"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24.9 s ± 300 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "for word in words:\n",
+    "    ed.eval(word, \"rebel\")\n",
+    "    ed.eval(word, \"statement\")\n",
+    "    ed.eval(word, \"sent\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import jellyfish as jf"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "21.8 s ± 390 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "for word in words:\n",
+    "    jf.levenshtein_distance(word, \"rebel\")\n",
+    "    jf.levenshtein_distance(word, \"statement\")\n",
+    "    jf.levenshtein_distance(word, \"sent\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
@@ -196,7 +510,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.11"
+   "version": "3.11.5"
   },
   "orig_nbformat": 4
  },
diff --git a/scripts/bench_container.cpp b/scripts/bench_container.cpp
new file mode 100644
index 00000000..6a77ba45
--- /dev/null
+++ b/scripts/bench_container.cpp
@@ -0,0 +1,89 @@
+/**
+ *  @file   bench_container.cpp
+ *  @brief  Benchmarks STL associative containers with string keys.
+ *
+ *  This file is the sibling of `bench_sort.cpp`, `bench_search.cpp` and `bench_token.cpp`.
+ *  It accepts a file with a list of words, constructs associative containers with string keys,
+ *  using `std::string`, `std::string_view`, `sz::string_view`, and `sz::string`, and then
+ *  evaluates the latency of lookups.
+ */
+#include <map>
+#include <unordered_map>
+
+#include <bench.hpp>
+
+using namespace ashvardanian::stringzilla::scripts;
+
+template <typename string_type_to, typename string_type_from>
+std::vector<string_type_to> to(std::vector<string_type_from> const &strings) {
+    std::vector<string_type_to> result;
+    result.reserve(strings.size());
+    for (string_type_from const &string : strings) result.push_back({string.data(), string.size()});
+    return result;
+}
+
+/**
+ *  @brief  Evaluation for search string operations: find.
+ */
+template <typename container_at>
+void bench(std::string name, std::vector<std::string_view> const &strings) {
+
+    using key_type = typename container_at::key_type;
+    std::vector<key_type> keys = to<key_type>(strings);
+
+    // Build up the container
+    container_at container;
+    for (key_type const &key : keys) container[key] = 0;
+
+    tracked_function_gt<unary_function_t> variant;
+    variant.name = name;
+    variant.results = bench_on_tokens(keys, [&](key_type const &key) {
+        container.find(key)->second++;
+        return key.size();
+    });
+
+    variant.print();
+}
+
+template <typename strings_type>
+void bench_tokens(strings_type const &strings) {
+    if (strings.size() == 0) return;
+    auto const &s = strings;
+
+    // StringZilla structures
+    bench<std::map<sz::string, int>>("map<sz::string>", s);
+    bench<std::map<sz::string_view, int>>("map<sz::string_view>", s);
+    bench<std::unordered_map<sz::string, int>>("unordered_map<sz::string>", s);
+    bench<std::unordered_map<sz::string_view, int>>("unordered_map<sz::string_view>", s);
+
+    // Pure STL
+    bench<std::map<std::string, int>>("map<std::string>", s);
+    bench<std::map<std::string_view, int>>("map<std::string_view>", s);
+    bench<std::unordered_map<std::string, int>>("unordered_map<std::string>", s);
+    bench<std::unordered_map<std::string_view, int>>("unordered_map<std::string_view>", s);
+
+    // STL structures with StringZilla operations
+    // bench<std::map<std::string, int, sz::less>>("map<std::string>", s);
+    // bench<std::map<std::string_view, int, sz::less>>("map<std::string_view>", s);
+    // bench<std::unordered_map<std::string, int, sz::hash, sz::equal_to>>("unordered_map<std::string>", s);
+    // bench<std::unordered_map<std::string_view, int, sz::hash, sz::equal_to>>("unordered_map<std::string_view>", s);
+}
+
+int main(int argc, char const **argv) {
+    std::printf("StringZilla. Starting search benchmarks.\n");
+
+    dataset_t dataset = make_dataset(argc, argv);
+
+    // Baseline benchmarks for real words, coming in all lengths
+    std::printf("Benchmarking on real words:\n");
+    bench_tokens(dataset.tokens);
+
+    // Run benchmarks on tokens of different length
+    for (std::size_t token_length : {1, 2, 3, 4, 5, 6, 7, 8, 16, 32}) {
+        std::printf("Benchmarking on real words of length %zu:\n", token_length);
+        bench_tokens(filter_by_length(dataset.tokens, token_length));
+    }
+
+    std::printf("All benchmarks passed.\n");
+    return 0;
+}
\ No newline at end of file
diff --git a/scripts/bench_search.cpp b/scripts/bench_search.cpp
new file mode 100644
index 00000000..c0411c40
--- /dev/null
+++ b/scripts/bench_search.cpp
@@ -0,0 +1,343 @@
+/**
+ *  @file   bench_search.cpp
+ *  @brief  Benchmarks for bidirectional string search operations - exact and TODO: approximate.
+ *
+ *  This file is the sibling of `bench_sort.cpp`, `bench_token.cpp` and `bench_similarity.cpp`.
+ *  It accepts a file with a list of words, and benchmarks the search operations on them.
+ *  Outside of present tokens also tries missing tokens.
+ */
+#include <cstring>    // `memmem`
+#include <functional> // `std::boyer_moore_searcher`
+
+#define SZ_USE_MISALIGNED_LOADS (1)
+#include <bench.hpp>
+
+using namespace ashvardanian::stringzilla::scripts;
+
+tracked_binary_functions_t find_functions() {
+    // ! Despite receiving string-views, following functions are assuming the strings are null-terminated.
+    auto wrap_sz = [](auto function) -> binary_function_t {
+        return binary_function_t([function](std::string_view h, std::string_view n) {
+            sz_cptr_t match = function(h.data(), h.size(), n.data(), n.size());
+            return (match ? match - h.data() : h.size());
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"std::string_view.find",
+         [](std::string_view h, std::string_view n) {
+             auto match = h.find(n);
+             return (match == std::string_view::npos ? h.size() : match);
+         }},
+        {"sz_find_serial", wrap_sz(sz_find_serial), true},
+#if SZ_USE_X86_AVX512
+        {"sz_find_avx512", wrap_sz(sz_find_avx512), true},
+#endif
+#if SZ_USE_X86_AVX2
+        {"sz_find_avx2", wrap_sz(sz_find_avx2), true},
+#endif
+#if SZ_USE_ARM_NEON
+        {"sz_find_neon", wrap_sz(sz_find_neon), true},
+#endif
+        {"strstr",
+         [](std::string_view h, std::string_view n) {
+             sz_cptr_t match = strstr(h.data(), n.data());
+             return (match ? match - h.data() : h.size());
+         }},
+#ifdef _GNU_SOURCE
+        {"memmem", // Not supported on MSVC
+         [](std::string_view h, std::string_view n) {
+             sz_cptr_t match = (sz_cptr_t)memmem(h.data(), h.size(), n.data(), n.size());
+             return (match ? match - h.data() : h.size());
+         }},
+#endif
+        {"std::search<>",
+         [](std::string_view h, std::string_view n) {
+             auto match = std::search(h.data(), h.data() + h.size(), n.data(), n.data() + n.size());
+             return (match - h.data());
+         }},
+#if __cpp_lib_boyer_moore_searcher
+        {"std::search<BM>",
+         [](std::string_view h, std::string_view n) {
+             auto match =
+                 std::search(h.data(), h.data() + h.size(), std::boyer_moore_searcher(n.data(), n.data() + n.size()));
+             return (match - h.data());
+         }},
+        {"std::search<BMH>",
+         [](std::string_view h, std::string_view n) {
+             auto match = std::search(h.data(), h.data() + h.size(),
+                                      std::boyer_moore_horspool_searcher(n.data(), n.data() + n.size()));
+             return (match - h.data());
+         }},
+#endif
+    };
+    return result;
+}
+
+tracked_binary_functions_t rfind_functions() {
+    // ! Despite receiving string-views, following functions are assuming the strings are null-terminated.
+    auto wrap_sz = [](auto function) -> binary_function_t {
+        return binary_function_t([function](std::string_view h, std::string_view n) {
+            sz_cptr_t match = function(h.data(), h.size(), n.data(), n.size());
+            return (match ? match - h.data() : 0);
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"std::string_view.rfind",
+         [](std::string_view h, std::string_view n) {
+             auto match = h.rfind(n);
+             return (match == std::string_view::npos ? 0 : match);
+         }},
+        {"sz_rfind_serial", wrap_sz(sz_rfind_serial), true},
+#if SZ_USE_X86_AVX512
+        {"sz_rfind_avx512", wrap_sz(sz_rfind_avx512), true},
+#endif
+#if SZ_USE_X86_AVX2
+        {"sz_rfind_avx2", wrap_sz(sz_rfind_avx2), true},
+#endif
+#if SZ_USE_ARM_NEON
+        {"sz_rfind_neon", wrap_sz(sz_rfind_neon), true},
+#endif
+        {"std::search<R>",
+         [](std::string_view h, std::string_view n) {
+             auto match = std::search(h.rbegin(), h.rend(), n.rbegin(), n.rend());
+             auto offset_from_end = (sz_ssize_t)(match - h.rbegin());
+             return h.size() - offset_from_end;
+         }},
+#if __cpp_lib_boyer_moore_searcher
+        {"std::search<R, BM>",
+         [](std::string_view h, std::string_view n) {
+             auto match = std::search(h.rbegin(), h.rend(), std::boyer_moore_searcher(n.rbegin(), n.rend()));
+             auto offset_from_end = (sz_ssize_t)(match - h.rbegin());
+             return h.size() - offset_from_end;
+         }},
+        {"std::search<R, BMH>",
+         [](std::string_view h, std::string_view n) {
+             auto match = std::search(h.rbegin(), h.rend(), std::boyer_moore_horspool_searcher(n.rbegin(), n.rend()));
+             auto offset_from_end = (sz_ssize_t)(match - h.rbegin());
+             return h.size() - offset_from_end;
+         }},
+#endif
+    };
+    return result;
+}
+
+tracked_binary_functions_t find_charset_functions() {
+    // ! Despite receiving string-views, following functions are assuming the strings are null-terminated.
+    auto wrap_sz = [](auto function) -> binary_function_t {
+        return binary_function_t([function](std::string_view h, std::string_view n) {
+            sz::char_set set;
+            for (auto c : n) set.add(c);
+            sz_cptr_t match = function(h.data(), h.size(), &set.raw());
+            return (match ? match - h.data() : h.size());
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"std::string_view.find_first_of",
+         [](std::string_view h, std::string_view n) {
+             auto match = h.find_first_of(n);
+             return (match == std::string_view::npos ? h.size() : match);
+         }},
+        {"sz_find_charset_serial", wrap_sz(sz_find_charset_serial), true},
+#if SZ_USE_X86_AVX512
+        {"sz_find_charset_avx512", wrap_sz(sz_find_charset_avx512), true},
+#endif
+#if SZ_USE_ARM_NEON
+        {"sz_find_charset_neon", wrap_sz(sz_find_charset_neon), true},
+#endif
+        {"strcspn", [](std::string_view h, std::string_view n) { return strcspn(h.data(), n.data()); }},
+    };
+    return result;
+}
+
+tracked_binary_functions_t rfind_charset_functions() {
+    // ! Despite receiving string-views, following functions are assuming the strings are null-terminated.
+    auto wrap_sz = [](auto function) -> binary_function_t {
+        return binary_function_t([function](std::string_view h, std::string_view n) {
+            sz::char_set set;
+            for (auto c : n) set.add(c);
+            sz_cptr_t match = function(h.data(), h.size(), &set.raw());
+            return (match ? match - h.data() : 0);
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"std::string_view.find_last_of",
+         [](std::string_view h, std::string_view n) {
+             auto match = h.find_last_of(n);
+             return (match == std::string_view::npos ? 0 : match);
+         }},
+        {"sz_rfind_charset_serial", wrap_sz(sz_rfind_charset_serial), true},
+#if SZ_USE_X86_AVX512
+        {"sz_rfind_charset_avx512", wrap_sz(sz_rfind_charset_avx512), true},
+#endif
+#if SZ_USE_ARM_NEON
+        {"sz_rfind_charset_neon", wrap_sz(sz_rfind_charset_neon), true},
+#endif
+    };
+    return result;
+}
+
+/**
+ *  @brief  Evaluation for search string operations: find.
+ */
+void bench_finds(std::string const &haystack, std::vector<std::string> const &strings,
+                 tracked_binary_functions_t &&variants) {
+
+    for (std::size_t variant_idx = 0; variant_idx != variants.size(); ++variant_idx) {
+        auto &variant = variants[variant_idx];
+
+        // Tests
+        if (variant.function && variant.needs_testing) {
+            bench_on_tokens(strings, [&](std::string_view needle) {
+                std::string_view remaining = haystack;
+                while (true) {
+                    auto baseline = variants[0].function(remaining, needle);
+                    auto result = variant.function(remaining, needle);
+                    if (result != baseline) {
+                        ++variant.failed_count;
+                        if (variant.failed_strings.empty()) {
+                            variant.failed_strings.push_back({remaining.data(), baseline + needle.size()});
+                            variant.failed_strings.push_back({needle.data(), needle.size()});
+                        }
+                    }
+
+                    if (baseline == remaining.size()) break;
+                    remaining = remaining.substr(baseline + 1);
+                }
+
+                return haystack.size();
+            });
+        }
+
+        // Benchmarks
+        if (variant.function) {
+            variant.results = bench_on_tokens(strings, [&](std::string_view needle) {
+                std::string_view remaining = haystack;
+                auto offset_from_start = variant.function(remaining, needle);
+                while (offset_from_start != remaining.size()) {
+                    remaining = remaining.substr(offset_from_start + 1);
+                    offset_from_start = variant.function(remaining, needle);
+                    do_not_optimize(offset_from_start);
+                }
+                return haystack.size();
+            });
+        }
+
+        variant.print();
+    }
+}
+
+/**
+ *  @brief  Evaluation for reverse order search string operations: find.
+ */
+void bench_rfinds(std::string const &haystack, std::vector<std::string> const &strings,
+                  tracked_binary_functions_t &&variants) {
+
+    for (std::size_t variant_idx = 0; variant_idx != variants.size(); ++variant_idx) {
+        auto &variant = variants[variant_idx];
+
+        // Tests
+        if (variant.function && variant.needs_testing) {
+            bench_on_tokens(strings, [&](std::string_view needle) {
+                std::string_view remaining = haystack;
+                while (true) {
+                    auto baseline = variants[0].function(remaining, needle);
+                    auto result = variant.function(remaining, needle);
+                    if (result != baseline) {
+                        ++variant.failed_count;
+                        if (variant.failed_strings.empty()) {
+                            variant.failed_strings.push_back(
+                                {remaining.data() + baseline, remaining.data() + remaining.size()});
+                            variant.failed_strings.push_back({needle.data(), needle.size()});
+                        }
+                    }
+
+                    if (baseline == remaining.size()) break;
+                    remaining = remaining.substr(0, baseline);
+                }
+
+                return haystack.size();
+            });
+        }
+
+        // Benchmarks
+        if (variant.function) {
+            variant.results = bench_on_tokens(strings, [&](std::string_view needle) {
+                std::string_view remaining = haystack;
+                auto offset_from_start = variant.function(remaining, needle);
+                while (offset_from_start != 0) {
+                    remaining = remaining.substr(0, offset_from_start - 1);
+                    offset_from_start = variant.function(remaining, needle);
+                    do_not_optimize(offset_from_start);
+                }
+                return haystack.size();
+            });
+        }
+
+        variant.print();
+    }
+}
+
+void bench_search(std::string const &haystack, std::vector<std::string> const &strings) {
+    if (strings.size() == 0) return;
+
+    bench_finds(haystack, strings, find_functions());
+    bench_rfinds(haystack, strings, rfind_functions());
+}
+
+int main(int argc, char const **argv) {
+    std::printf("StringZilla. Starting search benchmarks.\n");
+
+    dataset_t dataset = make_dataset(argc, argv);
+
+    // Splitting by new lines
+    std::printf("Benchmarking for a newline symbol:\n");
+    bench_finds(dataset.text, {"\n"}, find_functions());
+    bench_rfinds(dataset.text, {"\n"}, rfind_functions());
+
+    std::printf("Benchmarking for an [\\n\\r] RegEx:\n");
+    bench_finds(dataset.text, {sz::newlines()}, find_charset_functions());
+    bench_rfinds(dataset.text, {sz::newlines()}, rfind_charset_functions());
+
+    // Typical ASCII tokenization and validation benchmarks
+    std::printf("Benchmarking for whitespaces:\n");
+    bench_finds(dataset.text, {sz::whitespaces()}, find_charset_functions());
+    bench_rfinds(dataset.text, {sz::whitespaces()}, rfind_charset_functions());
+
+    std::printf("Benchmarking for HTML tag start/end:\n");
+    bench_finds(dataset.text, {"<>"}, find_charset_functions());
+    bench_rfinds(dataset.text, {"<>"}, rfind_charset_functions());
+
+    std::printf("Benchmarking for punctuation marks:\n");
+    bench_finds(dataset.text, {sz::punctuation()}, find_charset_functions());
+    bench_rfinds(dataset.text, {sz::punctuation()}, rfind_charset_functions());
+
+    std::printf("Benchmarking for non-printable characters:\n");
+    bench_finds(dataset.text, {sz::ascii_controls()}, find_charset_functions());
+    bench_rfinds(dataset.text, {sz::ascii_controls()}, rfind_charset_functions());
+
+    // Baseline benchmarks for present tokens, coming in all lengths
+    std::printf("Benchmarking on present lines:\n");
+    bench_search(dataset.text, {dataset.lines.begin(), dataset.lines.end()});
+    std::printf("Benchmarking on present tokens:\n");
+    bench_search(dataset.text, {dataset.tokens.begin(), dataset.tokens.end()});
+
+    // Run benchmarks on tokens of different length
+    for (std::size_t token_length : {1, 2, 3, 4, 5, 6, 7, 8, 16, 32}) {
+        std::printf("Benchmarking on present tokens of length %zu:\n", token_length);
+        bench_search(dataset.text, filter_by_length<std::string>(dataset.tokens, token_length));
+    }
+
+    // Run bechnmarks on abstract tokens of different length
+    for (std::size_t token_length : {1, 2, 3, 4, 5, 6, 7, 8, 16, 32}) {
+        std::printf("Benchmarking for missing tokens of length %zu:\n", token_length);
+        bench_search(dataset.text, std::vector<std::string> {
+                                       std::string(token_length, '\1'),
+                                       std::string(token_length, '\2'),
+                                       std::string(token_length, '\3'),
+                                       std::string(token_length, '\4'),
+                                   });
+    }
+
+    std::printf("All benchmarks passed.\n");
+    return 0;
+}
\ No newline at end of file
diff --git a/scripts/bench_search.py b/scripts/bench_search.py
new file mode 100644
index 00000000..0f49ffad
--- /dev/null
+++ b/scripts/bench_search.py
@@ -0,0 +1,106 @@
+import time
+import re
+import random
+from typing import List
+
+import fire
+
+from stringzilla import Str
+
+
+def log(name: str, haystack, patterns, operator: callable):
+    a = time.time_ns()
+    for pattern in patterns:
+        operator(haystack, pattern)
+    b = time.time_ns()
+    bytes_length = len(haystack) * len(patterns)
+    secs = (b - a) / 1e9
+    gb_per_sec = bytes_length / (1e9 * secs)
+    print(f"{name}: took {secs:} seconds ~ {gb_per_sec:.3f} GB/s")
+
+
+def find_all(haystack, pattern) -> int:
+    count, start = 0, 0
+    while True:
+        index = haystack.find(pattern, start)
+        if index == -1:
+            break
+        count += 1
+        start = index + 1
+    return count
+
+
+def rfind_all(haystack, pattern) -> int:
+    count, start = 0, len(haystack) - 1
+    while True:
+        index = haystack.rfind(pattern, 0, start + 1)
+        if index == -1:
+            break
+        count += 1
+        start = index - 1
+    return count
+
+
+def find_all_regex(haystack: str, characters: str) -> int:
+    regex_matcher = re.compile(f"[{characters}]")
+    count = 0
+    for _ in re.finditer(regex_matcher, haystack):
+        count += 1
+    return count
+
+
+def find_all_sets(haystack: Str, characters: str) -> int:
+    count, start = 0, 0
+    while True:
+        index = haystack.find_first_of(characters, start)
+        if index == -1:
+            break
+        count += 1
+        start = index + 1
+    return count
+
+
+def log_functionality(
+    tokens: List[str],
+    pythonic_str: str,
+    stringzilla_str: Str,
+):
+    log("str.find", pythonic_str, tokens, find_all)
+    log("Str.find", stringzilla_str, tokens, find_all)
+    log("str.rfind", pythonic_str, tokens, rfind_all)
+    log("Str.rfind", stringzilla_str, tokens, rfind_all)
+    log("re.finditer", pythonic_str, [r" \t\n\r"], find_all_regex)
+    log("Str.find_first_of", stringzilla_str, [r" \t\n\r"], find_all_sets)
+
+
+def bench(
+    haystack_path: str = None,
+    haystack_pattern: str = None,
+    haystack_length: int = None,
+):
+    if haystack_path:
+        pythonic_str: str = open(haystack_path, "r").read()
+    else:
+        haystack_length = int(haystack_length)
+        repetitions = haystack_length // len(haystack_pattern)
+        pythonic_str: str = haystack_pattern * repetitions
+
+    stringzilla_str = Str(pythonic_str)
+    tokens = pythonic_str.split()
+    total_tokens = len(tokens)
+    mean_token_length = sum(len(t) for t in tokens) / total_tokens
+
+    print(
+        f"Parsed the file with {total_tokens:,} words of {mean_token_length:.2f} mean length!"
+    )
+
+    tokens = random.sample(tokens, 100)
+    log_functionality(
+        tokens,
+        pythonic_str,
+        stringzilla_str,
+    )
+
+
+if __name__ == "__main__":
+    fire.Fire(bench)
diff --git a/scripts/bench_similarity.cpp b/scripts/bench_similarity.cpp
new file mode 100644
index 00000000..a24baa89
--- /dev/null
+++ b/scripts/bench_similarity.cpp
@@ -0,0 +1,130 @@
+/**
+ *  @file   bench_similarity.cpp
+ *  @brief  Benchmarks string similarity computations.
+ *
+ *  This file is the sibling of `bench_sort.cpp`, `bench_search.cpp` and `bench_token.cpp`.
+ *  It accepts a file with a list of words, and benchmarks the levenshtein edit-distance computations,
+ *  alignment scores, and fingerprinting techniques combined with the Hamming distance.
+ */
+#include <bench.hpp>
+#include <test.hpp> // `levenshtein_baseline`, `unary_substitution_costs`
+
+using namespace ashvardanian::stringzilla::scripts;
+
+using temporary_memory_t = std::vector<char>;
+temporary_memory_t temporary_memory;
+
+static void *allocate_from_vector(sz_size_t length, void *handle) {
+    temporary_memory_t &vec = *reinterpret_cast<temporary_memory_t *>(handle);
+    if (vec.size() < length) vec.resize(length);
+    return vec.data();
+}
+
+static void free_from_vector(void *buffer, sz_size_t length, void *handle) { sz_unused(buffer && length && handle); }
+
+tracked_binary_functions_t distance_functions() {
+    // Populate the unary substitutions matrix
+    static std::vector<std::int8_t> costs = unary_substitution_costs();
+
+    // Two rows of the Levenshtein matrix will occupy this much:
+    sz_memory_allocator_t alloc;
+    alloc.allocate = &allocate_from_vector;
+    alloc.free = &free_from_vector;
+    alloc.handle = &temporary_memory;
+
+    auto wrap_baseline = binary_function_t([](std::string_view a, std::string_view b) -> std::size_t {
+        return levenshtein_baseline(a.data(), a.length(), b.data(), b.length());
+    });
+    auto wrap_sz_distance = [alloc](auto function) mutable -> binary_function_t {
+        return binary_function_t([function, alloc](std::string_view a, std::string_view b) mutable -> std::size_t {
+            return function(a.data(), a.length(), b.data(), b.length(), (sz_size_t)0, &alloc);
+        });
+    };
+    auto wrap_sz_scoring = [alloc](auto function) mutable -> binary_function_t {
+        return binary_function_t([function, alloc](std::string_view a, std::string_view b) mutable -> std::size_t {
+            sz_memory_allocator_t *alloc_ptr = &alloc;
+            sz_ssize_t signed_result =
+                function(a.data(), a.length(), b.data(), b.length(),
+                         reinterpret_cast<sz_error_cost_t const *>(costs.data()), (sz_error_cost_t)-1, alloc_ptr);
+            return (std::size_t)(-signed_result);
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"naive", wrap_baseline},
+        {"sz_edit_distance", wrap_sz_distance(sz_edit_distance_serial), true},
+        {"sz_alignment_score", wrap_sz_scoring(sz_alignment_score_serial), true},
+#if SZ_USE_X86_AVX512
+        {"sz_edit_distance_avx512", wrap_sz_distance(sz_edit_distance_avx512), true},
+        {"sz_alignment_score_avx512", wrap_sz_scoring(sz_alignment_score_avx512), true},
+#endif
+    };
+    return result;
+}
+
+template <typename strings_at>
+void bench_similarity(strings_at &&strings) {
+    if (strings.size() == 0) return;
+    bench_binary_functions(strings, distance_functions());
+}
+
+void bench_similarity_on_bio_data() {
+    std::vector<std::string> proteins;
+
+    // A typical protein is 100-1000 amino acids long.
+    // The alphabet is generally 20 amino acids, but that won't affect the throughput.
+    char alphabet[4] = {'a', 'c', 'g', 't'};
+    constexpr std::size_t bio_samples = 128;
+    struct {
+        std::size_t length_lower_bound;
+        std::size_t length_upper_bound;
+        char const *name;
+    } bio_cases[] = {
+        {60, 60, "60 aminoacids"},              //
+        {100, 100, "100 aminoacids"},           //
+        {300, 300, "300 aminoacids"},           //
+        {1000, 1000, "1000 aminoacids"},        //
+        {100, 1000, "100-1000 aminoacids"},     //
+        {1000, 10000, "1000-10000 aminoacids"}, //
+    };
+    std::random_device random_device;
+    std::mt19937 generator(random_device());
+    for (auto bio_case : bio_cases) {
+        std::uniform_int_distribution<std::size_t> length_distribution(bio_case.length_lower_bound,
+                                                                       bio_case.length_upper_bound);
+        for (std::size_t i = 0; i != bio_samples; ++i) {
+            std::size_t length = length_distribution(generator);
+            std::string protein(length, 'a');
+            std::generate(protein.begin(), protein.end(), [&]() { return alphabet[generator() % sizeof(alphabet)]; });
+            proteins.push_back(protein);
+        }
+
+        std::printf("Benchmarking on protein-like sequences with %s:\n", bio_case.name);
+        bench_similarity(proteins);
+        proteins.clear();
+    }
+}
+
+void bench_similarity_on_input_data(int argc, char const **argv) {
+
+    dataset_t dataset = make_dataset(argc, argv);
+
+    // Baseline benchmarks for real words, coming in all lengths
+    std::printf("Benchmarking on real words:\n");
+    bench_similarity(dataset.tokens);
+
+    // Run benchmarks on tokens of different length
+    for (std::size_t token_length : {20}) {
+        std::printf("Benchmarking on real words of length %zu and longer:\n", token_length);
+        bench_similarity(filter_by_length(dataset.tokens, token_length, std::greater_equal<std::size_t> {}));
+    }
+}
+
+int main(int argc, char const **argv) {
+    std::printf("StringZilla. Starting similarity benchmarks.\n");
+
+    if (argc < 2) { bench_similarity_on_bio_data(); }
+    else { bench_similarity_on_input_data(argc, argv); }
+
+    std::printf("All benchmarks passed.\n");
+    return 0;
+}
diff --git a/scripts/bench_similarity.ipynb b/scripts/bench_similarity.ipynb
new file mode 100644
index 00000000..391bc783
--- /dev/null
+++ b/scripts/bench_similarity.ipynb
@@ -0,0 +1,347 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Benchmarks for String Similarity Scoring Functions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Install the most commonly used Python packages for string similarity scoring. This includes JellyFish for Levenshtein and Levenshten-Damerau distance, RapidFuzz for Levenshtein distance, and BioPython for Needleman-Wunsh scores among others."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!pip install rapidfuzz  # https://github.com/rapidfuzz/RapidFuzz\n",
+    "!pip install python-Levenshtein  # https://github.com/maxbachmann/python-Levenshtein\n",
+    "!pip install levenshtein # https://github.com/maxbachmann/Levenshtein\n",
+    "!pip install jellyfish # https://github.com/jamesturk/jellyfish/\n",
+    "!pip install editdistance # https://github.com/roy-ht/editdistance\n",
+    "!pip install distance # https://github.com/doukremt/distance\n",
+    "!pip install polyleven # https://github.com/fujimotos/polyleven\n",
+    "!pip install biopython # https://github.com/biopython/biopython\n",
+    "!pip install stringzilla # https://github.com/ashvardanian/stringzilla"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Levenshtein Distance Between Short English Words"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We will be conducting benchmarks on a real-world dataset of English words. Let's download the dataset and load it into memory."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!wget --no-clobber -O ../leipzig1M.txt https://introcs.cs.princeton.edu/python/42sort/leipzig1m.txt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "words = open(\"../leipzig1M.txt\", \"r\").read().split()\n",
+    "words = tuple(words)\n",
+    "print(f\"{len(words):,} words\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import random\n",
+    "\n",
+    "def checksum_distances(tokens, distance_function, n: int = 1000000):\n",
+    "    distances_sum = 0\n",
+    "    while n:\n",
+    "        a = random.choice(tokens)\n",
+    "        b = random.choice(tokens)\n",
+    "        distances_sum += distance_function(a, b)\n",
+    "        n -= 1\n",
+    "    return distances_sum"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import random"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "proteins = [''.join(random.choice('ACGT') for _ in range(10_000)) for _ in range(1_000)]\n",
+    "print(f\"{len(proteins):,} proteins\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import stringzilla as sz"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(words, sz.edit_distance)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(proteins, sz.edit_distance, 100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rapidfuzz.distance import Levenshtein as rf"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(words, rf.distance)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(proteins, rf.distance, 100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import editdistance as ed"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(words, ed.eval)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import jellyfish as jf"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(words, jf.levenshtein_distance)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import Levenshtein as le"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(words, le.distance)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Needleman-Wunsch Alignment Scores Between Random Protein Sequences"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "For Needleman-Wunsh, let's generate some random protein sequences:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from Bio import Align\n",
+    "from Bio.Align import substitution_matrices\n",
+    "aligner = Align.PairwiseAligner()\n",
+    "aligner.substitution_matrix = substitution_matrices.load(\"BLOSUM62\")\n",
+    "aligner.open_gap_score = 1\n",
+    "aligner.extend_gap_score = 1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "aligner.substitution_matrix"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's convert the BLOSUM matrix into a dense form with 256x256 elements. This will allow us to use the matrix with the Needleman-Wunsh algorithm implemented in StringZilla."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "\n",
+    "subs_packed = np.array(aligner.substitution_matrix).astype(np.int8)\n",
+    "subs_reconstructed = np.zeros((256, 256), dtype=np.int8)\n",
+    "\n",
+    "# Initialize all banned characters to a the largest possible penalty\n",
+    "subs_reconstructed.fill(127)\n",
+    "for packed_row, packed_row_aminoacid in enumerate(aligner.substitution_matrix.alphabet):\n",
+    "    for packed_column, packed_column_aminoacid in enumerate(aligner.substitution_matrix.alphabet):\n",
+    "        reconstructed_row = ord(packed_row_aminoacid)\n",
+    "        reconstructed_column = ord(packed_column_aminoacid)\n",
+    "        subs_reconstructed[reconstructed_row, reconstructed_column] = subs_packed[packed_row, packed_column]\n",
+    "\n",
+    "(subs_reconstructed < 127).sum()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "aligner.score(proteins[0], proteins[1])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sz.alignment_score(proteins[0], proteins[1], substitution_matrix=subs_reconstructed, gap_score=1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "def sz_score(a, b): return sz.alignment_score(a, b, substitution_matrix=subs_reconstructed, gap_score=1)\n",
+    "checksum_distances(proteins, sz_score, 100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%timeit\n",
+    "checksum_distances(proteins, aligner.score, 100)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "base",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.11"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/scripts/bench_sort.cpp b/scripts/bench_sort.cpp
new file mode 100644
index 00000000..b8554927
--- /dev/null
+++ b/scripts/bench_sort.cpp
@@ -0,0 +1,227 @@
+
+/**
+ *  @file   bench_sort.cpp
+ *  @brief  Benchmarks sorting, partitioning, and merging operations on string sequences.
+ *
+ *  This file is the sibling of `bench_similarity.cpp`, `bench_search.cpp` and `bench_token.cpp`.
+ *  It accepts a file with a list of words, and benchmarks the sorting operations on them.
+ */
+#include <memory> // `std::memcpy`
+
+#if __linux__ && defined(_GNU_SOURCE)
+#include <stdlib.h> // `qsort_r`
+#endif
+
+#include <bench.hpp>
+
+using namespace ashvardanian::stringzilla::scripts;
+
+using strings_t = std::vector<std::string>;
+using idx_t = sz_size_t;
+using permute_t = std::vector<sz_u64_t>;
+
+#pragma region - C callbacks
+
+static char const *get_start(sz_sequence_t const *array_c, sz_size_t i) {
+    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
+    return array[i].c_str();
+}
+
+static sz_size_t get_length(sz_sequence_t const *array_c, sz_size_t i) {
+    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
+    return array[i].size();
+}
+
+static sz_bool_t has_under_four_chars(sz_sequence_t const *array_c, sz_size_t i) {
+    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
+    return (sz_bool_t)(array[i].size() < 4);
+}
+
+static int _get_qsort_order(const void *a, const void *b, void *arg) {
+    sz_sequence_t *sequence = (sz_sequence_t *)arg;
+    sz_size_t idx_a = *(sz_size_t *)a;
+    sz_size_t idx_b = *(sz_size_t *)b;
+
+    const char *str_a = sequence->get_start(sequence, idx_a);
+    const char *str_b = sequence->get_start(sequence, idx_b);
+    sz_size_t len_a = sequence->get_length(sequence, idx_a);
+    sz_size_t len_b = sequence->get_length(sequence, idx_b);
+
+    int res = strncmp(str_a, str_b, len_a < len_b ? len_a : len_b);
+    return res ? res : (int)(len_a - len_b);
+}
+
+#pragma endregion
+
+void populate_from_file(std::string path, strings_t &strings,
+                        std::size_t limit = std::numeric_limits<std::size_t>::max()) {
+
+    std::ifstream f(path, std::ios::in);
+    std::string s;
+    while (strings.size() < limit && std::getline(f, s, ' ')) strings.push_back(s);
+}
+
+constexpr size_t offset_in_word = 0;
+
+static idx_t hybrid_sort_cpp(strings_t const &strings, sz_u64_t *order) {
+
+    // What if we take up-to 4 first characters and the index
+    for (size_t i = 0; i != strings.size(); ++i)
+        std::memcpy((char *)&order[i] + offset_in_word, strings[order[i]].c_str(),
+                    std::min<std::size_t>(strings[order[i]].size(), 4ul));
+
+    std::sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) {
+        char *i_bytes = (char *)&i;
+        char *j_bytes = (char *)&j;
+        return *(uint32_t *)(i_bytes + offset_in_word) < *(uint32_t *)(j_bytes + offset_in_word);
+    });
+
+    for (size_t i = 0; i != strings.size(); ++i) std::memset((char *)&order[i] + offset_in_word, 0, 4ul);
+
+    std::sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) { return strings[i] < strings[j]; });
+
+    return strings.size();
+}
+
+static idx_t hybrid_stable_sort_cpp(strings_t const &strings, sz_u64_t *order) {
+
+    // What if we take up-to 4 first characters and the index
+    for (size_t i = 0; i != strings.size(); ++i)
+        std::memcpy((char *)&order[i] + offset_in_word, strings[order[i]].c_str(),
+                    std::min<std::size_t>(strings[order[i]].size(), 4ull));
+
+    std::stable_sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) {
+        char *i_bytes = (char *)&i;
+        char *j_bytes = (char *)&j;
+        return *(uint32_t *)(i_bytes + offset_in_word) < *(uint32_t *)(j_bytes + offset_in_word);
+    });
+
+    for (size_t i = 0; i != strings.size(); ++i) std::memset((char *)&order[i] + offset_in_word, 0, 4ul);
+
+    std::stable_sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) { return strings[i] < strings[j]; });
+
+    return strings.size();
+}
+
+void expect_partitioned_by_length(strings_t const &strings, permute_t const &permute) {
+    if (!std::is_partitioned(permute.begin(), permute.end(), [&](size_t i) { return strings[i].size() < 4; }))
+        throw std::runtime_error("Partitioning failed!");
+}
+
+void expect_sorted(strings_t const &strings, permute_t const &permute) {
+    if (!std::is_sorted(permute.begin(), permute.end(),
+                        [&](std::size_t i, std::size_t j) { return strings[i] < strings[j]; }))
+        throw std::runtime_error("Sorting failed!");
+}
+
+void expect_same(permute_t const &permute_base, permute_t const &permute_new) {
+    if (!std::equal(permute_base.begin(), permute_base.end(), permute_new.begin()))
+        throw std::runtime_error("Permutations differ!");
+}
+
+template <typename algo_at>
+void bench_permute(char const *name, strings_t &strings, permute_t &permute, algo_at &&algo) {
+    namespace stdc = std::chrono;
+    using stdcc = stdc::high_resolution_clock;
+    constexpr std::size_t iterations = 3;
+    stdcc::time_point t1 = stdcc::now();
+
+    // Run multiple iterations
+    for (std::size_t i = 0; i != iterations; ++i) {
+        std::iota(permute.begin(), permute.end(), 0);
+        algo(strings, permute);
+    }
+
+    // Measure elapsed time
+    stdcc::time_point t2 = stdcc::now();
+    double dif = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count();
+    double milisecs = dif / (iterations * 1e6);
+    std::printf("Elapsed time is %.2lf miliseconds/iteration for %s.\n", milisecs, name);
+}
+
+int main(int argc, char const **argv) {
+    std::printf("StringZilla. Starting sorting benchmarks.\n");
+    dataset_t dataset = make_dataset(argc, argv);
+    strings_t strings {dataset.tokens.begin(), dataset.tokens.end()};
+
+    permute_t permute_base, permute_new;
+    permute_base.resize(strings.size());
+    permute_new.resize(strings.size());
+
+    // Partitioning
+    {
+        std::printf("---- Partitioning:\n");
+        bench_permute("std::partition", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
+            std::partition(permute.begin(), permute.end(), [&](size_t i) { return strings[i].size() < 4; });
+        });
+        expect_partitioned_by_length(strings, permute_base);
+
+        bench_permute("std::stable_partition", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
+            std::stable_partition(permute.begin(), permute.end(), [&](size_t i) { return strings[i].size() < 4; });
+        });
+        expect_partitioned_by_length(strings, permute_base);
+
+        bench_permute("sz_partition", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
+            sz_sequence_t array;
+            array.order = permute.data();
+            array.count = strings.size();
+            array.handle = &strings;
+            sz_partition(&array, &has_under_four_chars);
+        });
+        expect_partitioned_by_length(strings, permute_new);
+        // TODO: expect_same(permute_base, permute_new);
+    }
+
+    // Sorting
+    {
+        std::printf("---- Sorting:\n");
+        bench_permute("std::sort", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
+            std::sort(permute.begin(), permute.end(), [&](idx_t i, idx_t j) { return strings[i] < strings[j]; });
+        });
+        expect_sorted(strings, permute_base);
+
+        bench_permute("sz_sort", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
+            sz_sequence_t array;
+            array.order = permute.data();
+            array.count = strings.size();
+            array.handle = &strings;
+            array.get_start = get_start;
+            array.get_length = get_length;
+            sz_sort(&array);
+        });
+        expect_sorted(strings, permute_new);
+
+#if __linux__ && defined(_GNU_SOURCE)
+        bench_permute("qsort_r", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
+            sz_sequence_t array;
+            array.order = permute.data();
+            array.count = strings.size();
+            array.handle = &strings;
+            array.get_start = get_start;
+            array.get_length = get_length;
+            qsort_r(array.order, array.count, sizeof(sz_u64_t), _get_qsort_order, &array);
+        });
+        expect_sorted(strings, permute_new);
+#else
+        sz_unused(_get_qsort_order);
+#endif
+
+        bench_permute("hybrid_sort_cpp", strings, permute_new,
+                      [](strings_t const &strings, permute_t &permute) { hybrid_sort_cpp(strings, permute.data()); });
+        expect_sorted(strings, permute_new);
+
+        std::printf("---- Stable Sorting:\n");
+        bench_permute("std::stable_sort", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
+            std::stable_sort(permute.begin(), permute.end(), [&](idx_t i, idx_t j) { return strings[i] < strings[j]; });
+        });
+        expect_sorted(strings, permute_base);
+
+        bench_permute(
+            "hybrid_stable_sort_cpp", strings, permute_base,
+            [](strings_t const &strings, permute_t &permute) { hybrid_stable_sort_cpp(strings, permute.data()); });
+        expect_sorted(strings, permute_new);
+        expect_same(permute_base, permute_new);
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/scripts/bench.py b/scripts/bench_sort.py
similarity index 76%
rename from scripts/bench.py
rename to scripts/bench_sort.py
index a7c864fb..b96847c9 100644
--- a/scripts/bench.py
+++ b/scripts/bench_sort.py
@@ -21,16 +21,6 @@ def log_functionality(
     stringzilla_str: Str,
     stringzilla_file: File,
 ):
-    log("str.contains", bytes_length, lambda: pattern in pythonic_str)
-    log("Str.contains", bytes_length, lambda: pattern in stringzilla_str)
-    if stringzilla_file:
-        log("File.contains", bytes_length, lambda: pattern in stringzilla_file)
-
-    log("str.count", bytes_length, lambda: pythonic_str.count(pattern))
-    log("Str.count", bytes_length, lambda: stringzilla_str.count(pattern))
-    if stringzilla_file:
-        log("File.count", bytes_length, lambda: stringzilla_file.count(pattern))
-
     log("str.split", bytes_length, lambda: pythonic_str.split(pattern))
     log("Str.split", bytes_length, lambda: stringzilla_str.split(pattern))
     if stringzilla_file:
@@ -43,10 +33,10 @@ def log_functionality(
 
 
 def bench(
-    needle: str,
     haystack_path: str = None,
     haystack_pattern: str = None,
     haystack_length: int = None,
+    needle: str = None,
 ):
     if haystack_path:
         pythonic_str: str = open(haystack_path, "r").read()
diff --git a/scripts/bench_token.cpp b/scripts/bench_token.cpp
new file mode 100644
index 00000000..4eb1db04
--- /dev/null
+++ b/scripts/bench_token.cpp
@@ -0,0 +1,211 @@
+/**
+ *  @file   bench_token.cpp
+ *  @brief  Benchmarks token-level operations like hashing, equality, ordering, and copies.
+ *
+ *  This file is the sibling of `bench_sort.cpp`, `bench_search.cpp` and `bench_similarity.cpp`.
+ */
+#include <bench.hpp>
+#include <test.hpp> // `random_string`
+
+#include <stringzilla/experimental.h> // `sz_hashes_neon`
+
+using namespace ashvardanian::stringzilla::scripts;
+
+tracked_unary_functions_t hashing_functions() {
+    auto wrap_sz = [](auto function) -> unary_function_t {
+        return unary_function_t([function](std::string_view s) { return function(s.data(), s.size()); });
+    };
+    tracked_unary_functions_t result = {
+        {"sz_hash_serial", wrap_sz(sz_hash_serial)},
+        {"std::hash", [](std::string_view s) { return std::hash<std::string_view> {}(s); }},
+    };
+    return result;
+}
+
+tracked_unary_functions_t sliding_hashing_functions(std::size_t window_width, std::size_t step) {
+    auto wrap_sz = [=](auto function) -> unary_function_t {
+        return unary_function_t([function, window_width, step](std::string_view s) {
+            sz_size_t mixed_hash = 0;
+            function(s.data(), s.size(), window_width, step, _sz_hashes_fingerprint_scalar_callback, &mixed_hash);
+            return mixed_hash;
+        });
+    };
+    std::string suffix = std::to_string(window_width) + ":step" + std::to_string(step);
+    tracked_unary_functions_t result = {
+#if SZ_USE_X86_AVX512
+        {"sz_hashes_avx512:" + suffix, wrap_sz(sz_hashes_avx512)},
+#endif
+#if SZ_USE_X86_AVX2
+        {"sz_hashes_avx2:" + suffix, wrap_sz(sz_hashes_avx2)},
+#endif
+#if SZ_USE_ARM_NEON
+        {"sz_hashes_neon_naive:" + suffix, wrap_sz(sz_hashes_neon_naive)},
+        {"sz_hashes_neon_readahead:" + suffix, wrap_sz(sz_hashes_neon_readahead)},
+        {"sz_hashes_neon_reusing_loads:" + suffix, wrap_sz(sz_hashes_neon_reusing_loads)},
+#endif
+        {"sz_hashes_serial:" + suffix, wrap_sz(sz_hashes_serial)},
+    };
+    return result;
+}
+
+tracked_unary_functions_t fingerprinting_functions(std::size_t window_width = 8, std::size_t fingerprint_bytes = 4096) {
+    using fingerprint_slot_t = std::uint8_t;
+    static std::vector<fingerprint_slot_t> fingerprint;
+    fingerprint.resize(fingerprint_bytes / sizeof(fingerprint_slot_t));
+    auto wrap_sz = [](auto function) -> unary_function_t {
+        return unary_function_t([function](std::string_view s) {
+            sz_size_t mixed_hash = 0;
+            sz_unused(s);
+            return mixed_hash;
+        });
+    };
+    tracked_unary_functions_t result = {};
+    sz_unused(window_width && fingerprint_bytes);
+    sz_unused(wrap_sz);
+    return result;
+}
+
+tracked_unary_functions_t random_generation_functions(std::size_t token_length) {
+    static std::vector<char> buffer;
+    if (buffer.size() < token_length) buffer.resize(token_length);
+
+    auto suffix = ", " + std::to_string(token_length) + " chars";
+    tracked_unary_functions_t result = {
+        {"std::rand % uint8" + suffix, unary_function_t([token_length](std::string_view alphabet) -> std::size_t {
+             using max_alphabet_size_t = std::uint8_t;
+             auto max_alphabet_size = static_cast<max_alphabet_size_t>(alphabet.size());
+             for (std::size_t i = 0; i < token_length; ++i) { buffer[i] = alphabet[std::rand() % max_alphabet_size]; }
+             return token_length;
+         })},
+        {"std::uniform_int<uint8>" + suffix, unary_function_t([token_length](std::string_view alphabet) -> std::size_t {
+             randomize_string(buffer.data(), token_length, alphabet.data(), alphabet.size());
+             return token_length;
+         })},
+        {"sz::randomize" + suffix, unary_function_t([token_length](std::string_view alphabet) -> std::size_t {
+             sz::string_span span(buffer.data(), token_length);
+             sz::randomize(span, global_random_generator(), alphabet);
+             return token_length;
+         })},
+    };
+    return result;
+}
+
+tracked_binary_functions_t equality_functions() {
+    auto wrap_sz = [](auto function) -> binary_function_t {
+        return binary_function_t([function](std::string_view a, std::string_view b) {
+            return (a.size() == b.size() && function(a.data(), b.data(), a.size()));
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"std::string_view.==", [](std::string_view a, std::string_view b) { return (a == b); }},
+        {"sz_equal_serial", wrap_sz(sz_equal_serial), true},
+#if SZ_USE_X86_AVX512
+        {"sz_equal_avx512", wrap_sz(sz_equal_avx512), true},
+#endif
+        {"memcmp",
+         [](std::string_view a, std::string_view b) {
+             return (a.size() == b.size() && memcmp(a.data(), b.data(), a.size()) == 0);
+         }},
+    };
+    return result;
+}
+
+tracked_binary_functions_t ordering_functions() {
+    auto wrap_sz = [](auto function) -> binary_function_t {
+        return binary_function_t([function](std::string_view a, std::string_view b) {
+            return function(a.data(), a.size(), b.data(), b.size());
+        });
+    };
+    tracked_binary_functions_t result = {
+        {"std::string_view.compare",
+         [](std::string_view a, std::string_view b) {
+             auto order = a.compare(b);
+             return (order == 0 ? sz_equal_k : (order < 0 ? sz_less_k : sz_greater_k));
+         }},
+        {"sz_order_serial", wrap_sz(sz_order_serial), true},
+        {"memcmp",
+         [](std::string_view a, std::string_view b) {
+             auto order = memcmp(a.data(), b.data(), a.size() < b.size() ? a.size() : b.size());
+             return order != 0 ? (a.size() == b.size() ? (order < 0 ? sz_less_k : sz_greater_k)
+                                                       : (a.size() < b.size() ? sz_less_k : sz_greater_k))
+                               : sz_equal_k;
+         }},
+    };
+    return result;
+}
+
+template <typename string_type>
+void bench_dereferencing(std::string name, std::vector<string_type> strings) {
+    auto func = unary_function_t([](std::string_view s) { return s.size(); });
+    tracked_unary_functions_t converts = {{name, func}};
+    bench_unary_functions(strings, converts);
+}
+
+template <typename strings_type>
+void bench(strings_type &&strings) {
+    if (strings.size() == 0) return;
+
+    // Benchmark logical operations
+    bench_unary_functions(strings, hashing_functions());
+    bench_unary_functions(strings, sliding_hashing_functions(8, 1));
+    bench_unary_functions(strings, fingerprinting_functions());
+    bench_binary_functions(strings, equality_functions());
+    bench_binary_functions(strings, ordering_functions());
+
+    // Benchmark the cost of converting `std::string` and `sz::string` to `std::string_view`.
+    // ! The results on a mixture of short and long strings should be similar.
+    // ! If the dataset is made of exclusively short or long strings, STL will look much better
+    // ! in this micro-benchmark, as the correct branch of the SSO will be predicted every time.
+    bench_dereferencing<std::string>("std::string -> std::string_view", {strings.begin(), strings.end()});
+    bench_dereferencing<sz::string>("sz::string -> std::string_view", {strings.begin(), strings.end()});
+}
+
+void bench_on_input_data(int argc, char const **argv) {
+    dataset_t dataset = make_dataset(argc, argv);
+
+    std::printf("Benchmarking on the entire dataset:\n");
+    bench_unary_functions(dataset.tokens, random_generation_functions(100));
+    bench_unary_functions(dataset.tokens, random_generation_functions(20));
+    bench_unary_functions(dataset.tokens, random_generation_functions(5));
+
+    // When performing fingerprinting, it's extremely important to:
+    //      1. Have small output fingerprints that fit the cache.
+    //      2. Have that memory in close affinity to the core, ideally on stack, to avoid cache coherency problems.
+    // This introduces an additional challenge for efficient fingerprinting, as the CPU caches vary a lot.
+    // On the Intel Sapphire Rapids 6455B Gold CPU they are 96 KiB x2 for L1d, 4 MiB x2 for L2.
+    // Spilling into the L3 is a bad idea.
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, sliding_hashing_functions(7, 1));
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, sliding_hashing_functions(17, 4));
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, sliding_hashing_functions(33, 8));
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, sliding_hashing_functions(127, 16));
+
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, hashing_functions());
+
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, fingerprinting_functions(128, 4 * 1024));
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, fingerprinting_functions(128, 64 * 1024));
+    bench_unary_functions<std::vector<std::string_view>>({dataset.text}, fingerprinting_functions(128, 1024 * 1024));
+
+    // Baseline benchmarks for real words, coming in all lengths
+    std::printf("Benchmarking on real words:\n");
+    bench(dataset.tokens);
+
+    // Run benchmarks on tokens of different length
+    for (std::size_t token_length : {1, 2, 3, 4, 5, 6, 7, 8, 16, 32}) {
+        std::printf("Benchmarking on real words of length %zu:\n", token_length);
+        bench(filter_by_length(dataset.tokens, token_length));
+    }
+}
+
+void bench_on_synthetic_data() {
+    // Generate some random words
+}
+
+int main(int argc, char const **argv) {
+    std::printf("StringZilla. Starting token-level benchmarks.\n");
+
+    if (argc < 2) { bench_on_synthetic_data(); }
+    else { bench_on_input_data(argc, argv); }
+
+    std::printf("All benchmarks passed.\n");
+    return 0;
+}
\ No newline at end of file
diff --git a/scripts/bench_token.ipynb b/scripts/bench_token.ipynb
new file mode 100644
index 00000000..590b8edc
--- /dev/null
+++ b/scripts/bench_token.ipynb
@@ -0,0 +1,606 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Benchmarks for Token-Leen String Operations"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We will be conducting benchmarks on a real-world dataset of English words. Feel free to replace with your favorite dataset :)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "File ‘../leipzig1M.txt’ already there; not retrieving.\n"
+     ]
+    }
+   ],
+   "source": [
+    "!wget --no-clobber -O ../leipzig1M.txt https://introcs.cs.princeton.edu/python/42sort/leipzig1m.txt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "171,285,845 words\n"
+     ]
+    }
+   ],
+   "source": [
+    "text = open(\"../xlsum.csv\", \"r\").read(1024 * 1024 * 1024)\n",
+    "words = text.split()\n",
+    "words = tuple(words)\n",
+    "print(f\"{len(words):,} words\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('to', 2),\n",
+       " ('Châu,', 6),\n",
+       " ('возможность', 22),\n",
+       " ('la', 2),\n",
+       " (\"doesn't\", 7),\n",
+       " ('सकता', 12),\n",
+       " ('and', 3),\n",
+       " ('Interestingly,', 14),\n",
+       " ('have', 4),\n",
+       " ('Зрители', 14)]"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import random\n",
+    "\n",
+    "word_examples = random.choices(words, k=10)\n",
+    "word_lengths = [len(s.encode('utf-8')) for s in word_examples]\n",
+    "\n",
+    "list(zip(word_examples, word_lengths))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Hashing"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Throughput"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's chack how long it takes the default Python implementation to hash the entire dataset."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1.09 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit -n 1 -r 1\n",
+    "text.__hash__()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "9.75 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit -n 1 -r 1\n",
+    "for word in words: word.__hash__()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's compare to StringZilla's implementation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import stringzilla as sz\n",
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "7.69 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit -n 1 -r 1\n",
+    "sz.hash(text)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "13.7 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit -n 1 -r 1\n",
+    "for word in words: sz.hash(word)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Quality and Collisions Frequency"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "One of the most important qualities of the hash function is it's resistence to collisions. Let's check how many collisions we have in the dataset.\n",
+    "For that, we will create a bitset using NumPy with more than `len(word)` bits for each word in the dataset. Then, we will hash each word and set the corresponding bit in the bitset. Finally, we will count the number of set bits in the bitset. The more empty spots are left in the bitset, the weaker is the function."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def count_populated(words, hasher) -> int:\n",
+    "    slots_count = len(words) * 2\n",
+    "    bitset = np.zeros(slots_count, dtype=bool)\n",
+    "\n",
+    "    # Hash each word and set the corresponding bit in the bitset\n",
+    "    for word in words:\n",
+    "        hash_value = hasher(word) % slots_count\n",
+    "        bitset[hash_value] = True\n",
+    "\n",
+    "    # Count the number of set bits\n",
+    "    return np.sum(bitset)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "7,982,184 unique words\n"
+     ]
+    }
+   ],
+   "source": [
+    "unique_words = set(words)\n",
+    "print(f\"{len(unique_words):,} unique words\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `hash`: 92,147 ~ 1.1544%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_default = count_populated(words, hash)\n",
+    "collisions_default = len(unique_words) - populated_default\n",
+    "print(f\"Collisions for `hash`: {collisions_default:,} ~ {collisions_default / len(unique_words):.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `sz.hash`: 97,183 ~ 1.2175%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_stringzilla = count_populated(words, sz.hash)\n",
+    "collisions_stringzilla = len(unique_words) - populated_stringzilla\n",
+    "print(f\"Collisions for `sz.hash`: {collisions_stringzilla:,} ~ {collisions_stringzilla / len(unique_words):.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Base10 Numbers"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Benchmarks on small datasets may not be very representative. Let's generate 4 Billion unique strings of different length and check the quality of the hash function on them. To make that efficient, let's define a generator expression that will generate the strings on the fly. Each string is a printed integer representation from 0 to 4 Billion."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def count_populated_synthetic(make_generator, n, hasher) -> int:\n",
+    "    slots_count = n * 2\n",
+    "    bitset = np.zeros(slots_count, dtype=bool)\n",
+    "\n",
+    "    # Hash each word and set the corresponding bit in the bitset\n",
+    "    for word in make_generator(n):\n",
+    "        hash_value = hasher(word) % (slots_count)\n",
+    "        bitset[hash_value] = True\n",
+    "\n",
+    "    # Count the number of set bits\n",
+    "    return np.sum(bitset)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n = 256 * 1024 * 1024"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def generate_printed_numbers_until(n):\n",
+    "    \"\"\"Generator expression to yield strings of printed integers from 0 to n.\"\"\"\n",
+    "    for i in range(n):\n",
+    "        yield str(i)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `hash`: 57,197,029 ~ 21.3076%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_default = count_populated_synthetic(generate_printed_numbers_until, n, hash)\n",
+    "collisions_default = n - populated_default\n",
+    "print(f\"Collisions for `hash`: {collisions_default:,} ~ {collisions_default / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `sz.hash`: 57,820,385 ~ 21.5398%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_sz = count_populated_synthetic(generate_printed_numbers_until, n, sz.hash)\n",
+    "collisions_sz = n - populated_sz\n",
+    "print(f\"Collisions for `sz.hash`: {collisions_sz:,} ~ {collisions_sz / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Base64 Numbers"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import base64\n",
+    "\n",
+    "def int_to_base64(n):\n",
+    "    byte_length = (n.bit_length() + 7) // 8\n",
+    "    byte_array = n.to_bytes(byte_length, 'big')\n",
+    "    base64_string = base64.b64encode(byte_array)\n",
+    "    return base64_string.decode() \n",
+    "\n",
+    "def generate_base64_numbers_until(n):\n",
+    "    \"\"\"Generator expression to yield strings of printed integers from 0 to n.\"\"\"\n",
+    "    for i in range(n):\n",
+    "        yield int_to_base64(i)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `hash`: 57,197,478 ~ 21.3077%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_default = count_populated_synthetic(generate_base64_numbers_until, n, hash)\n",
+    "collisions_default = n - populated_default\n",
+    "print(f\"Collisions for `hash`: {collisions_default:,} ~ {collisions_default / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `sz.hash`: 224,998,905 ~ 83.8186%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_sz = count_populated_synthetic(generate_base64_numbers_until, n, sz.hash)\n",
+    "collisions_sz = n - populated_sz\n",
+    "print(f\"Collisions for `sz.hash`: {collisions_sz:,} ~ {collisions_sz / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Base256 Representations"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def generate_base256_numbers_until(n) -> bytes:\n",
+    "    \"\"\"Generator a 4-byte long binary string wilth all possible values of `uint32_t` until value `n`.\"\"\"\n",
+    "    for i in range(n):\n",
+    "        yield i.to_bytes(4, byteorder='big')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `hash`: 57,195,602 ~ 21.3070%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_default = count_populated_synthetic(generate_base256_numbers_until, n, hash)\n",
+    "collisions_default = n - populated_default\n",
+    "print(f\"Collisions for `hash`: {collisions_default:,} ~ {collisions_default / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `sz.hash`: 59,905,848 ~ 22.3167%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_sz = count_populated_synthetic(generate_base256_numbers_until, n, sz.hash)\n",
+    "collisions_sz = n - populated_sz\n",
+    "print(f\"Collisions for `sz.hash`: {collisions_sz:,} ~ {collisions_sz / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Protein Sequences"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Benchmarks on small datasets may not be very representative. Let's generate 4 Billion unique strings of different length and check the quality of the hash function on them. To make that efficient, let's define a generator expression that will generate the strings on the fly. Each string is a printed integer representation from 0 to 4 Billion."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n = 1 * 1024 * 1024"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def generate_proteins(n):\n",
+    "    \"\"\"Generator expression to yield strings of printed integers from 0 to n.\"\"\"\n",
+    "    alphabet = 'ACGT'\n",
+    "    for _ in range(n):\n",
+    "        yield ''.join(random.choices(alphabet, k=300))\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `hash`: 223,848 ~ 21.3478%\n"
+     ]
+    }
+   ],
+   "source": [
+    "populated_default = count_populated_synthetic(generate_proteins, n, hash)\n",
+    "collisions_default = n - populated_default\n",
+    "print(f\"Collisions for `hash`: {collisions_default:,} ~ {collisions_default / n:.4%}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collisions for `sz.hash`: 223,995 ~ 21.3618%\n"
+     ]
+    },
+    {
+     "ename": "",
+     "evalue": "",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31mThe Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details."
+     ]
+    }
+   ],
+   "source": [
+    "populated_sz = count_populated_synthetic(generate_proteins, n, sz.hash)\n",
+    "collisions_sz = n - populated_sz\n",
+    "print(f\"Collisions for `sz.hash`: {collisions_sz:,} ~ {collisions_sz / n:.4%}\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "base",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 00000000..600e5758
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+# This Bash script compiles the CMake-based project with different compilers for different verrsions of C++
+# This is what should happen if only GCC 12 is installed and we are running on Sapphire Rapids.
+#
+#       cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+#           -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 \
+#           -DSTRINGZILLA_TARGET_ARCH="sandybridge" -B build_release/gcc-12-sandybridge && \
+#           cmake --build build_release/gcc-12-sandybridge --config Release
+#       cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+#           -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 \
+#           -DSTRINGZILLA_TARGET_ARCH="haswell" -B build_release/gcc-12-haswell && \
+#           cmake --build build_release/gcc-12-haswell --config Release
+#       cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+#           -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 \
+#           -DSTRINGZILLA_TARGET_ARCH="sapphirerapids" -B build_release/gcc-12-sapphirerapids && \
+#           cmake --build build_release/gcc-12-sapphirerapids --config Release
+
+# Array of target architectures
+declare -a architectures=("sandybridge" "haswell" "sapphirerapids")
+
+# Function to get installed versions of a compiler
+get_versions() {
+    local compiler_prefix=$1
+    local versions=()
+
+    echo "Checking for compilers in /usr/bin with prefix: $compiler_prefix"
+
+    # Check if the directory /usr/bin exists and is a directory
+    if [ -d "/usr/bin" ]; then
+        for version in /usr/bin/${compiler_prefix}-*; do
+            echo "Checking: $version"
+            if [[ -x "$version" ]]; then
+                local ver=${version##*-}
+                echo "Found compiler version: $ver"
+                versions+=("$ver")
+            fi
+        done
+    else
+        echo "/usr/bin does not exist or is not a directory"
+    fi
+    
+    echo ${versions[@]}
+}
+
+# Get installed versions of GCC and Clang
+gcc_versions=$(get_versions gcc)
+clang_versions=$(get_versions clang)
+
+# Compile for each combination of compiler and architecture
+for arch in "${ARCHS[@]}"; do
+    for gcc_version in $gcc_versions; do
+        cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+              -DCMAKE_CXX_COMPILER=g++-$gcc_version -DCMAKE_C_COMPILER=gcc-$gcc_version \
+              -DSTRINGZILLA_TARGET_ARCH="$arch" -B "build_release/gcc-$gcc_version-$arch" && \
+              cmake --build "build_release/gcc-$gcc_version-$arch" --config Release
+    done
+
+    for clang_version in $clang_versions; do
+        cmake -DCMAKE_BUILD_TYPE=Release -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+              -DCMAKE_CXX_COMPILER=clang++-$clang_version -DCMAKE_C_COMPILER=clang-$clang_version \
+              -DSTRINGZILLA_TARGET_ARCH="$arch" -B "build_release/clang-$clang_version-$arch" && \
+              cmake --build "build_release/clang-$clang_version-$arch" --config Release
+    done
+done
+
diff --git a/setup.py b/setup.py
index 12357369..cbfdead2 100644
--- a/setup.py
+++ b/setup.py
@@ -2,63 +2,130 @@
 import sys
 import platform
 from setuptools import setup, Extension
+from typing import List, Tuple
+import sysconfig
+import glob
 
 import numpy as np
 
-compile_args = []
-link_args = []
-macros_args = []
 
-if sys.platform == "linux":
-    compile_args.append("-std=c99")
-    compile_args.append("-O3")
-    compile_args.append("-pedantic")
-    compile_args.append("-Wno-unknown-pragmas")
-    compile_args.append("-fdiagnostics-color=always")
-    compile_args.append("-fopenmp")
-    link_args.append("-lgomp")
-
-    compiler = ""
+def get_compiler() -> str:
     if platform.python_implementation() == "CPython":
         compiler = platform.python_compiler().lower()
-        if "gcc" in compiler:
-            compiler = "gcc"
-        elif "clang" in compiler:
-            compiler = "llvm"
+        return "gcc" if "gcc" in compiler else "llvm" if "clang" in compiler else ""
+    return ""
+
+
+def is_64bit_x86() -> bool:
+    arch = platform.machine()
+    return arch == "x86_64" or arch == "i386"
+
 
+def is_64bit_arm() -> bool:
     arch = platform.machine()
-    if arch == "x86_64" or arch == "i386":
-        compile_args.append("-march=haswell")
-    elif arch.startswith("arm"):
+    return arch.startswith("arm")
+
+
+def linux_settings() -> Tuple[List[str], List[str], List[Tuple[str]]]:
+    compile_args = [
+        "-std=c99",  # use the C 99 language dialect
+        "-pedantic",  # stick close to the C language standard, avoid compiler extensions
+        "-O3",  # maximum optimization level
+        "-fdiagnostics-color=always",  # color console output
+        "-Wno-unknown-pragmas",  # like: `pragma region` and some unrolls
+        "-Wno-unused-function",  # like: ... declared ‘static’ but never defined
+        "-Wno-incompatible-pointer-types",  # like: passing argument 4 of ‘sz_export_prefix_u32’ from incompatible pointer type
+        "-Wno-discarded-qualifiers",  # like: passing argument 1 of ‘free’ discards ‘const’ qualifier from pointer target type
+        "-fPIC",  # to enable dynamic dispatch
+    ]
+    link_args = [
+        "-fPIC",  # to enable dynamic dispatch
+    ]
+
+    # GCC is our primary compiler, so when packaging the library, even if the current machine
+    # doesn't support AVX-512 or SVE, still precompile those.
+    macros_args = [
+        ("SZ_USE_X86_AVX512", "1" if is_64bit_x86() else "0"),
+        ("SZ_USE_X86_AVX2", "1" if is_64bit_x86() else "0"),
+        ("SZ_USE_ARM_SVE", "1" if is_64bit_arm() else "0"),
+        ("SZ_USE_ARM_NEON", "1" if is_64bit_arm() else "0"),
+    ]
+
+    if is_64bit_arm():
         compile_args.append("-march=armv8-a+simd")
-        if compiler == "gcc":
-            compile_args.extend(["-mfpu=neon", "-mfloat-abi=hard"])
 
+    return compile_args, link_args, macros_args
+
+
+def darwin_settings() -> Tuple[List[str], List[str], List[Tuple[str]]]:
+    compile_args = [
+        "-std=c99",  # use the C 99 language dialect
+        "-pedantic",  # stick close to the C language standard, avoid compiler extensions
+        "-O3",  # maximum optimization level
+        "-fcolor-diagnostics",  # color console output
+        "-Wno-unknown-pragmas",  # like: `pragma region` and some unrolls
+        "-Wno-incompatible-function-pointer-types",
+        "-Wno-incompatible-pointer-types",  # like: passing argument 4 of ‘sz_export_prefix_u32’ from incompatible pointer type
+        "-Wno-discarded-qualifiers",  # like: passing argument 1 of ‘free’ discards ‘const’ qualifier from pointer target type
+        "-fPIC",  # to enable dynamic dispatch
+    ]
+    link_args = [
+        "-fPIC",  # to enable dynamic dispatch
+    ]
+
+    # Apple Clang doesn't support the `-march=native` argument,
+    # so we must pre-set the CPU generation. Technically the last Intel-based Apple
+    # product was the 2021 MacBook Pro, which had the "Coffee Lake" architecture.
+    # During Universal builds, however, even AVX header cause compilation errors.
+    can_use_avx2 = is_64bit_x86() and sysconfig.get_platform().startswith("universal")
+    macros_args = [
+        ("SZ_USE_X86_AVX512", "0"),
+        ("SZ_USE_X86_AVX2", "1" if can_use_avx2 else "0"),
+        ("SZ_USE_ARM_SVE", "0"),
+        ("SZ_USE_ARM_NEON", "1" if is_64bit_arm() else "0"),
+    ]
+
+    return compile_args, link_args, macros_args
+
+
+def windows_settings() -> Tuple[List[str], List[str], List[Tuple[str]]]:
+    compile_args = [
+        "/std:c99",  # use the C 99 language dialect
+        "/Wall",  # stick close to the C language standard, avoid compiler extensions
+        "/O2",  # maximum optimization level
+    ]
+
+    # Detect supported architectures for MSVC.
+    macros_args = []
+    if "AVX512" in platform.processor():
+        macros_args.append(("SZ_USE_X86_AVX512", "1"))
+        compile_args.append("/arch:AVX512")
+    if "AVX2" in platform.processor():
+        macros_args.append(("SZ_USE_X86_AVX2", "1"))
+        compile_args.append("/arch:AVX2")
+
+    link_args = []
+    return compile_args, link_args, macros_args
+
+
+if sys.platform == "linux":
+    compile_args, link_args, macros_args = linux_settings()
 
-if sys.platform == "darwin":
-    compile_args.append("-std=c99")
-    compile_args.append("-O3")
-    compile_args.append("-pedantic")
-    compile_args.append("-Wno-unknown-pragmas")
-    compile_args.append("-Wno-incompatible-function-pointer-types")
-    compile_args.append("-Wno-incompatible-pointer-types")
-    compile_args.append("-fcolor-diagnostics")
-    compile_args.append("-Xpreprocessor -fopenmp")
-    link_args.append("-Xpreprocessor -lomp")
+elif sys.platform == "darwin":
+    compile_args, link_args, macros_args = darwin_settings()
 
-if sys.platform == "win32":
-    compile_args.append("/std:c99")
-    compile_args.append("/O2")
+elif sys.platform == "win32":
+    compile_args, link_args, macros_args = windows_settings()
 
 
 ext_modules = [
     Extension(
         "stringzilla",
-        ["python/lib.c"],
-        include_dirs=["stringzilla", np.get_include()],
+        ["python/lib.c"] + glob.glob("c/*.c"),
+        include_dirs=["include", np.get_include()],
         extra_compile_args=compile_args,
         extra_link_args=link_args,
-        define_macros=macros_args,
+        define_macros=[("SZ_DYNAMIC_DISPATCH", "1")] + macros_args,
     ),
 ]
 
@@ -75,7 +142,7 @@
     name=__lib_name__,
     version=__version__,
     author="Ash Vardanian",
-    description="Crunch multi-gigabyte strings with ease",
+    description="SIMD-accelerated string search, sort, hashes, fingerprints, & edit distances",
     long_description=long_description,
     long_description_content_type="text/markdown",
     license="Apache-2.0",
diff --git a/stringzilla/stringzilla.h b/stringzilla/stringzilla.h
deleted file mode 100644
index b93e191a..00000000
--- a/stringzilla/stringzilla.h
+++ /dev/null
@@ -1,1273 +0,0 @@
-#ifndef STRINGZILLA_H_
-#define STRINGZILLA_H_
-
-#if defined(__AVX2__)
-#include <x86intrin.h>
-#endif
-
-#if defined(__ARM_NEON)
-#include <arm_neon.h>
-#endif
-
-#if defined(__ARM_FEATURE_CRC32)
-#include <arm_acle.h>
-#endif
-
-/**
- *  Intrinsics aliases for MSVC, GCC, and Clang.
- */
-#ifdef _MSC_VER
-#include <intrin.h>
-#define popcount64 __popcnt64
-#define ctz64 _tzcnt_u64
-#define clz64 _lzcnt_u64
-#else
-#define popcount64 __builtin_popcountll
-#define ctz64 __builtin_ctzll
-#define clz64 __builtin_clzll
-#endif
-
-/**
- *  @brief  Generally `NULL` is coming from locale.h, stddef.h, stdio.h, stdlib.h, string.h, time.h, and wchar.h,
- *          according to the C standard.
- */
-#ifndef NULL
-#define NULL ((void *)0)
-#endif
-
-/**
- * @brief   Compile-time assert macro.
- */
-#define SZ_STATIC_ASSERT(condition, name)                \
-    typedef struct {                                     \
-        int static_assert_##name : (condition) ? 1 : -1; \
-    } sz_static_assert_##name##_t
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- *  @brief  Analogous to `sz_size_t` and `std::sz_size_t`, unsigned integer, identical to pointer size.
- *          64-bit on most platforms where pointers are 64-bit.
- *          32-bit on platforms where pointers are 32-bit.
- */
-#if defined(__LP64__) || defined(_LP64) || defined(__x86_64__) || defined(_WIN64)
-typedef unsigned long long sz_size_t;
-#else
-typedef unsigned sz_size_t;
-#endif
-SZ_STATIC_ASSERT(sizeof(sz_size_t) == sizeof(void *), sz_size_t_must_be_pointer_size);
-
-typedef int sz_bool_t;                 // Only one relevant bit
-typedef unsigned sz_u32_t;             // Always 32 bits
-typedef unsigned long long sz_u64_t;   // Always 64 bits
-typedef char const *sz_string_start_t; // A type alias for `char const * `
-
-/**
- *  @brief  For faster bounded Levenstein (Edit) distance computation no more than 255 characters are supported.
- */
-typedef unsigned char levenstein_distance_t;
-
-/**
- *  @brief  Helper construct for higher-level bindings.
- */
-typedef struct sz_string_view_t {
-    sz_string_start_t start;
-    sz_size_t length;
-} sz_string_view_t;
-
-/**
- *  @brief  Internal data-structure, used to address "anomalies" (often prefixes),
- *          during substring search. Always a 32-bit unsigned integer, containing 4 chars.
- */
-typedef union _sz_anomaly_t {
-    unsigned u32;
-    unsigned char u8s[4];
-} _sz_anomaly_t;
-
-/**
- *  @brief  This is a slightly faster alternative to `strncmp(a, b, length) == 0`.
- *          Doesn't provide major performance improvements, but helps avoid the LibC dependency.
- *  @return 1 for `true`, and 0 for `false`.
- */
-inline static sz_bool_t sz_equal(sz_string_start_t a, sz_string_start_t b, sz_size_t length) {
-    sz_string_start_t const a_end = a + length;
-    while (a != a_end && *a == *b) a++, b++;
-    return a_end == a;
-}
-
-/**
- *  @brief  Count the number of occurrences of a @b single-character needle in an arbitrary length haystack.
- *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
- */
-inline static sz_size_t sz_count_char_swar(sz_string_start_t const haystack,
-                                           sz_size_t const haystack_length,
-                                           sz_string_start_t const needle) {
-
-    sz_size_t result = 0;
-    sz_string_start_t text = haystack;
-    sz_string_start_t const end = haystack + haystack_length;
-
-    // Process the misaligned head, to void UB on unaligned 64-bit loads.
-    for (; ((sz_size_t)text & 7ull) && text < end; ++text) result += *text == *needle;
-
-    // This code simulates hyper-scalar execution, comparing 8 characters at a time.
-    sz_u64_t nnnnnnnn = *needle;
-    nnnnnnnn |= nnnnnnnn << 8;
-    nnnnnnnn |= nnnnnnnn << 16;
-    nnnnnnnn |= nnnnnnnn << 32;
-    for (; text + 8 <= end; text += 8) {
-        sz_u64_t text_slice = *(sz_u64_t const *)text;
-        sz_u64_t match_indicators = ~(text_slice ^ nnnnnnnn);
-        match_indicators &= match_indicators >> 1;
-        match_indicators &= match_indicators >> 2;
-        match_indicators &= match_indicators >> 4;
-        match_indicators &= 0x0101010101010101;
-        result += popcount64(match_indicators);
-    }
-
-    for (; text < end; ++text) result += *text == *needle;
-    return result;
-}
-
-/**
- *  @brief  Find the first occurrence of a @b single-character needle in an arbitrary length haystack.
- *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
- *          Identical to `memchr(haystack, needle[0], haystack_length)`.
- */
-inline static sz_string_start_t sz_find_1char_swar(sz_string_start_t const haystack,
-                                                   sz_size_t const haystack_length,
-                                                   sz_string_start_t const needle) {
-
-    sz_string_start_t text = haystack;
-    sz_string_start_t const end = haystack + haystack_length;
-
-    // Process the misaligned head, to void UB on unaligned 64-bit loads.
-    for (; ((sz_size_t)text & 7ull) && text < end; ++text)
-        if (*text == *needle) return text;
-
-    // This code simulates hyper-scalar execution, analyzing 8 offsets at a time.
-    sz_u64_t nnnnnnnn = *needle;
-    nnnnnnnn |= nnnnnnnn << 8;  // broadcast `needle` into `nnnnnnnn`
-    nnnnnnnn |= nnnnnnnn << 16; // broadcast `needle` into `nnnnnnnn`
-    nnnnnnnn |= nnnnnnnn << 32; // broadcast `needle` into `nnnnnnnn`
-    for (; text + 8 <= end; text += 8) {
-        sz_u64_t text_slice = *(sz_u64_t const *)text;
-        sz_u64_t match_indicators = ~(text_slice ^ nnnnnnnn);
-        match_indicators &= match_indicators >> 1;
-        match_indicators &= match_indicators >> 2;
-        match_indicators &= match_indicators >> 4;
-        match_indicators &= 0x0101010101010101;
-
-        if (match_indicators != 0) return text + ctz64(match_indicators) / 8;
-    }
-
-    for (; text < end; ++text)
-        if (*text == *needle) return text;
-    return NULL;
-}
-
-/**
- *  @brief  Find the last occurrence of a @b single-character needle in an arbitrary length haystack.
- *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
- *          Identical to `memrchr(haystack, needle[0], haystack_length)`.
- */
-inline static sz_string_start_t sz_rfind_1char_swar(sz_string_start_t const haystack,
-                                                    sz_size_t const haystack_length,
-                                                    sz_string_start_t const needle) {
-
-    sz_string_start_t const end = haystack + haystack_length;
-    sz_string_start_t text = end - 1;
-
-    // Process the misaligned head, to void UB on unaligned 64-bit loads.
-    for (; ((sz_size_t)text & 7ull) && text >= haystack; --text)
-        if (*text == *needle) return text;
-
-    // This code simulates hyper-scalar execution, analyzing 8 offsets at a time.
-    sz_u64_t nnnnnnnn = *needle;
-    nnnnnnnn |= nnnnnnnn << 8;  // broadcast `needle` into `nnnnnnnn`
-    nnnnnnnn |= nnnnnnnn << 16; // broadcast `needle` into `nnnnnnnn`
-    nnnnnnnn |= nnnnnnnn << 32; // broadcast `needle` into `nnnnnnnn`
-    for (; text - 8 >= haystack; text -= 8) {
-        sz_u64_t text_slice = *(sz_u64_t const *)text;
-        sz_u64_t match_indicators = ~(text_slice ^ nnnnnnnn);
-        match_indicators &= match_indicators >> 1;
-        match_indicators &= match_indicators >> 2;
-        match_indicators &= match_indicators >> 4;
-        match_indicators &= 0x0101010101010101;
-
-        if (match_indicators != 0) return text - 8 + clz64(match_indicators) / 8;
-    }
-
-    for (; text >= haystack; --text)
-        if (*text == *needle) return text;
-    return NULL;
-}
-
-/**
- *  @brief  Find the first occurrence of a @b two-character needle in an arbitrary length haystack.
- *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
- */
-inline static sz_string_start_t sz_find_2char_swar(sz_string_start_t const haystack,
-                                                   sz_size_t const haystack_length,
-                                                   sz_string_start_t const needle) {
-
-    sz_string_start_t text = haystack;
-    sz_string_start_t const end = haystack + haystack_length;
-
-    // Process the misaligned head, to void UB on unaligned 64-bit loads.
-    for (; ((sz_size_t)text & 7ull) && text + 2 <= end; ++text)
-        if (text[0] == needle[0] && text[1] == needle[1]) return text;
-
-    // This code simulates hyper-scalar execution, analyzing 7 offsets at a time.
-    sz_u64_t nnnn = ((sz_u64_t)(needle[0]) << 0) | ((sz_u64_t)(needle[1]) << 8); // broadcast `needle` into `nnnn`
-    nnnn |= nnnn << 16;                                                          // broadcast `needle` into `nnnn`
-    nnnn |= nnnn << 32;                                                          // broadcast `needle` into `nnnn`
-    for (; text + 8 <= end; text += 7) {
-        sz_u64_t text_slice = *(sz_u64_t const *)text;
-        sz_u64_t even_indicators = ~(text_slice ^ nnnn);
-        sz_u64_t odd_indicators = ~((text_slice << 8) ^ nnnn);
-
-        // For every even match - 2 char (16 bits) must be identical.
-        even_indicators &= even_indicators >> 1;
-        even_indicators &= even_indicators >> 2;
-        even_indicators &= even_indicators >> 4;
-        even_indicators &= even_indicators >> 8;
-        even_indicators &= 0x0001000100010001;
-
-        // For every odd match - 2 char (16 bits) must be identical.
-        odd_indicators &= odd_indicators >> 1;
-        odd_indicators &= odd_indicators >> 2;
-        odd_indicators &= odd_indicators >> 4;
-        odd_indicators &= odd_indicators >> 8;
-        odd_indicators &= 0x0001000100010000;
-
-        if (even_indicators + odd_indicators) {
-            sz_u64_t match_indicators = even_indicators | (odd_indicators >> 8);
-            return text + ctz64(match_indicators) / 8;
-        }
-    }
-
-    for (; text + 2 <= end; ++text)
-        if (text[0] == needle[0] && text[1] == needle[1]) return text;
-    return NULL;
-}
-
-/**
- *  @brief  Find the first occurrence of a three-character needle in an arbitrary length haystack.
- *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
- */
-inline static sz_string_start_t sz_find_3char_swar(sz_string_start_t const haystack,
-                                                   sz_size_t const haystack_length,
-                                                   sz_string_start_t const needle) {
-
-    sz_string_start_t text = haystack;
-    sz_string_start_t end = haystack + haystack_length;
-
-    // Process the misaligned head, to void UB on unaligned 64-bit loads.
-    for (; ((sz_size_t)text & 7ull) && text + 3 <= end; ++text)
-        if (text[0] == needle[0] && text[1] == needle[1] && text[2] == needle[2]) return text;
-
-    // This code simulates hyper-scalar execution, analyzing 6 offsets at a time.
-    // We have two unused bytes at the end.
-    sz_u64_t nn =                      // broadcast `needle` into `nn`
-        (sz_u64_t)(needle[0] << 0) |   // broadcast `needle` into `nn`
-        ((sz_u64_t)(needle[1]) << 8) | // broadcast `needle` into `nn`
-        ((sz_u64_t)(needle[2]) << 16); // broadcast `needle` into `nn`
-    nn |= nn << 24;                    // broadcast `needle` into `nn`
-    nn <<= 16;                         // broadcast `needle` into `nn`
-
-    for (; text + 8 <= end; text += 6) {
-        sz_u64_t text_slice = *(sz_u64_t const *)text;
-        sz_u64_t first_indicators = ~(text_slice ^ nn);
-        sz_u64_t second_indicators = ~((text_slice << 8) ^ nn);
-        sz_u64_t third_indicators = ~((text_slice << 16) ^ nn);
-        // For every first match - 3 chars (24 bits) must be identical.
-        // For that merge every byte state and then combine those three-way.
-        first_indicators &= first_indicators >> 1;
-        first_indicators &= first_indicators >> 2;
-        first_indicators &= first_indicators >> 4;
-        first_indicators =
-            (first_indicators >> 16) & (first_indicators >> 8) & (first_indicators >> 0) & 0x0000010000010000;
-
-        // For every second match - 3 chars (24 bits) must be identical.
-        // For that merge every byte state and then combine those three-way.
-        second_indicators &= second_indicators >> 1;
-        second_indicators &= second_indicators >> 2;
-        second_indicators &= second_indicators >> 4;
-        second_indicators =
-            (second_indicators >> 16) & (second_indicators >> 8) & (second_indicators >> 0) & 0x0000010000010000;
-
-        // For every third match - 3 chars (24 bits) must be identical.
-        // For that merge every byte state and then combine those three-way.
-        third_indicators &= third_indicators >> 1;
-        third_indicators &= third_indicators >> 2;
-        third_indicators &= third_indicators >> 4;
-        third_indicators =
-            (third_indicators >> 16) & (third_indicators >> 8) & (third_indicators >> 0) & 0x0000010000010000;
-
-        sz_u64_t match_indicators = first_indicators | (second_indicators >> 8) | (third_indicators >> 16);
-        if (match_indicators != 0) return text + ctz64(match_indicators) / 8;
-    }
-
-    for (; text + 3 <= end; ++text)
-        if (text[0] == needle[0] && text[1] == needle[1] && text[2] == needle[2]) return text;
-    return NULL;
-}
-
-/**
- *  @brief  Find the first occurrence of a @b four-character needle in an arbitrary length haystack.
- *          This implementation uses hardware-agnostic SWAR technique, to process 8 characters at a time.
- */
-inline static sz_string_start_t sz_find_4char_swar(sz_string_start_t const haystack,
-                                                   sz_size_t const haystack_length,
-                                                   sz_string_start_t const needle) {
-
-    sz_string_start_t text = haystack;
-    sz_string_start_t end = haystack + haystack_length;
-
-    // Process the misaligned head, to void UB on unaligned 64-bit loads.
-    for (; ((sz_size_t)text & 7ull) && text + 4 <= end; ++text)
-        if (text[0] == needle[0] && text[1] == needle[1] && text[2] == needle[2] && text[3] == needle[3]) return text;
-
-    // This code simulates hyper-scalar execution, analyzing 4 offsets at a time.
-    sz_u64_t nn = (sz_u64_t)(needle[0] << 0) | ((sz_u64_t)(needle[1]) << 8) | ((sz_u64_t)(needle[2]) << 16) |
-                  ((sz_u64_t)(needle[3]) << 24);
-    nn |= nn << 32;
-
-    //
-    unsigned char offset_in_slice[16] = {0};
-    offset_in_slice[0x2] = offset_in_slice[0x6] = offset_in_slice[0xA] = offset_in_slice[0xE] = 1;
-    offset_in_slice[0x4] = offset_in_slice[0xC] = 2;
-    offset_in_slice[0x8] = 3;
-
-    // We can perform 5 comparisons per load, but it's easier to perform 4, minimizing the size of the lookup table.
-    for (; text + 8 <= end; text += 4) {
-        sz_u64_t text_slice = *(sz_u64_t const *)text;
-        sz_u64_t text01 = (text_slice & 0x00000000FFFFFFFF) | ((text_slice & 0x000000FFFFFFFF00) << 24);
-        sz_u64_t text23 = ((text_slice & 0x0000FFFFFFFF0000) >> 16) | ((text_slice & 0x00FFFFFFFF000000) << 8);
-        sz_u64_t text01_indicators = ~(text01 ^ nn);
-        sz_u64_t text23_indicators = ~(text23 ^ nn);
-
-        // For every first match - 4 chars (32 bits) must be identical.
-        text01_indicators &= text01_indicators >> 1;
-        text01_indicators &= text01_indicators >> 2;
-        text01_indicators &= text01_indicators >> 4;
-        text01_indicators &= text01_indicators >> 8;
-        text01_indicators &= text01_indicators >> 16;
-        text01_indicators &= 0x0000000100000001;
-
-        // For every first match - 4 chars (32 bits) must be identical.
-        text23_indicators &= text23_indicators >> 1;
-        text23_indicators &= text23_indicators >> 2;
-        text23_indicators &= text23_indicators >> 4;
-        text23_indicators &= text23_indicators >> 8;
-        text23_indicators &= text23_indicators >> 16;
-        text23_indicators &= 0x0000000100000001;
-
-        if (text01_indicators + text23_indicators) {
-            // Assuming we have performed 4 comparisons, we can only have 2^4=16 outcomes.
-            // Which is small enough for a lookup table.
-            unsigned char match_indicators = (unsigned char)(          //
-                (text01_indicators >> 31) | (text01_indicators << 0) | //
-                (text23_indicators >> 29) | (text23_indicators << 2));
-            return text + offset_in_slice[match_indicators];
-        }
-    }
-
-    for (; text + 4 <= end; ++text)
-        if (text[0] == needle[0] && text[1] == needle[1] && text[2] == needle[2] && text[3] == needle[3]) return text;
-    return NULL;
-}
-
-/**
- *  @brief  Trivial substring search with scalar SWAR code. Instead of comparing characters one-by-one
- *          it compares 4-byte anomalies first, most commonly prefixes. It's computationally cheaper.
- *          Matching performance fluctuates between 1 GB/s and 3,5 GB/s per core.
- */
-inline static sz_string_start_t sz_find_substring_swar( //
-    sz_string_start_t const haystack,
-    sz_size_t const haystack_length,
-    sz_string_start_t const needle,
-    sz_size_t const needle_length) {
-
-    if (haystack_length < needle_length) return NULL;
-
-    sz_size_t anomaly_offset = 0;
-    switch (needle_length) {
-    case 0: return NULL;
-    case 1: return sz_find_1char_swar(haystack, haystack_length, needle);
-    case 2: return sz_find_2char_swar(haystack, haystack_length, needle);
-    case 3: return sz_find_3char_swar(haystack, haystack_length, needle);
-    case 4: return sz_find_4char_swar(haystack, haystack_length, needle);
-    default: {
-        sz_string_start_t text = haystack;
-        sz_string_start_t const end = haystack + haystack_length;
-
-        _sz_anomaly_t n_anomaly, h_anomaly;
-        sz_size_t const n_suffix_len = needle_length - 4 - anomaly_offset;
-        sz_string_start_t n_suffix_ptr = needle + 4 + anomaly_offset;
-        n_anomaly.u8s[0] = needle[anomaly_offset];
-        n_anomaly.u8s[1] = needle[anomaly_offset + 1];
-        n_anomaly.u8s[2] = needle[anomaly_offset + 2];
-        n_anomaly.u8s[3] = needle[anomaly_offset + 3];
-        h_anomaly.u8s[0] = haystack[0];
-        h_anomaly.u8s[1] = haystack[1];
-        h_anomaly.u8s[2] = haystack[2];
-        h_anomaly.u8s[3] = haystack[3];
-
-        text += anomaly_offset;
-        while (text + needle_length <= end) {
-            h_anomaly.u8s[3] = text[3];
-            if (h_anomaly.u32 == n_anomaly.u32)                     // Match anomaly.
-                if (sz_equal(text + 4, n_suffix_ptr, n_suffix_len)) // Match suffix.
-                    return text;
-
-            h_anomaly.u32 >>= 8;
-            ++text;
-        }
-        return NULL;
-    }
-    }
-}
-
-/**
- *  Helper function, used in substring search operations.
- */
-inline static void _sz_find_substring_populate_anomaly( //
-    sz_string_start_t const needle,
-    sz_size_t const needle_length,
-    _sz_anomaly_t *anomaly_out,
-    _sz_anomaly_t *mask_out) {
-
-    _sz_anomaly_t anomaly;
-    _sz_anomaly_t mask;
-    switch (needle_length) {
-    case 1:
-        mask.u8s[0] = 0xFF, mask.u8s[1] = mask.u8s[2] = mask.u8s[3] = 0;
-        anomaly.u8s[0] = needle[0], anomaly.u8s[1] = anomaly.u8s[2] = anomaly.u8s[3] = 0;
-        break;
-    case 2:
-        mask.u8s[0] = mask.u8s[1] = 0xFF, mask.u8s[2] = mask.u8s[3] = 0;
-        anomaly.u8s[0] = needle[0], anomaly.u8s[1] = needle[1], anomaly.u8s[2] = anomaly.u8s[3] = 0;
-        break;
-    case 3:
-        mask.u8s[0] = mask.u8s[1] = mask.u8s[2] = 0xFF, mask.u8s[3] = 0;
-        anomaly.u8s[0] = needle[0], anomaly.u8s[1] = needle[1], anomaly.u8s[2] = needle[2], anomaly.u8s[3] = 0;
-        break;
-    default:
-        mask.u32 = 0xFFFFFFFF;
-        anomaly.u8s[0] = needle[0], anomaly.u8s[1] = needle[1], anomaly.u8s[2] = needle[2], anomaly.u8s[3] = needle[3];
-        break;
-    }
-    *anomaly_out = anomaly;
-    *mask_out = mask;
-}
-
-#if defined(__AVX2__)
-
-/**
- *  @brief  Substring-search implementation, leveraging x86 AVX2 intrinsics and speculative
- *          execution capabilities on modern CPUs. Performing 4 unaligned vector loads per cycle
- *          was practically more efficient than loading once and shifting around, as introduces
- *          less data dependencies.
- */
-inline static sz_string_start_t sz_find_substring_avx2(sz_string_start_t const haystack,
-                                                       sz_size_t const haystack_length,
-                                                       sz_string_start_t const needle,
-                                                       sz_size_t const needle_length) {
-
-    // Precomputed constants
-    sz_string_start_t const end = haystack + haystack_length;
-    _sz_anomaly_t anomaly;
-    _sz_anomaly_t mask;
-    _sz_find_substring_populate_anomaly(needle, needle_length, &anomaly, &mask);
-    __m256i const anomalies = _mm256_set1_epi32(anomaly.u32);
-    __m256i const masks = _mm256_set1_epi32(mask.u32);
-
-    // Top level for-loop changes dramatically.
-    // In sequential computing model for 32 offsets we would do:
-    //  + 32 comparions.
-    //  + 32 branches.
-    // In vectorized computations models:
-    //  + 4 vectorized comparisons.
-    //  + 4 movemasks.
-    //  + 3 bitwise ANDs.
-    //  + 1 heavy (but very unlikely) branch.
-    sz_string_start_t text = haystack;
-    while (text + needle_length + 32 <= end) {
-
-        // Performing many unaligned loads ends up being faster than loading once and shuffling around.
-        __m256i texts0 = _mm256_and_si256(_mm256_loadu_si256((__m256i const *)(text + 0)), masks);
-        int matches0 = _mm256_movemask_epi8(_mm256_cmpeq_epi32(texts0, anomalies));
-        __m256i texts1 = _mm256_and_si256(_mm256_loadu_si256((__m256i const *)(text + 1)), masks);
-        int matches1 = _mm256_movemask_epi8(_mm256_cmpeq_epi32(texts1, anomalies));
-        __m256i text2 = _mm256_and_si256(_mm256_loadu_si256((__m256i const *)(text + 2)), masks);
-        int matches2 = _mm256_movemask_epi8(_mm256_cmpeq_epi32(text2, anomalies));
-        __m256i texts3 = _mm256_and_si256(_mm256_loadu_si256((__m256i const *)(text + 3)), masks);
-        int matches3 = _mm256_movemask_epi8(_mm256_cmpeq_epi32(texts3, anomalies));
-
-        if (matches0 | matches1 | matches2 | matches3) {
-            int matches =                 //
-                (matches0 & 0x11111111) | //
-                (matches1 & 0x22222222) | //
-                (matches2 & 0x44444444) | //
-                (matches3 & 0x88888888);
-            sz_size_t first_match_offset = ctz64(matches);
-            if (needle_length > 4) {
-                if (sz_equal(text + first_match_offset + 4, needle + 4, needle_length - 4)) {
-                    return text + first_match_offset;
-                }
-                else { text += first_match_offset + 1; }
-            }
-            else { return text + first_match_offset; }
-        }
-        else { text += 32; }
-    }
-
-    // Don't forget the last (up to 35) characters.
-    return sz_find_substring_swar(text, end - text, needle, needle_length);
-}
-
-#endif // x86 AVX2
-
-#if defined(__ARM_NEON)
-
-/**
- *  @brief  Substring-search implementation, leveraging Arm Neon intrinsics and speculative
- *          execution capabilities on modern CPUs. Performing 4 unaligned vector loads per cycle
- *          was practically more efficient than loading once and shifting around, as introduces
- *          less data dependencies.
- */
-inline static sz_string_start_t sz_find_substring_neon(sz_string_start_t const haystack,
-                                                       sz_size_t const haystack_length,
-                                                       sz_string_start_t const needle,
-                                                       sz_size_t const needle_length) {
-
-    // Precomputed constants
-    sz_string_start_t const end = haystack + haystack_length;
-    _sz_anomaly_t anomaly;
-    _sz_anomaly_t mask;
-    _sz_find_substring_populate_anomaly(needle, needle_length, &anomaly, &mask);
-    uint32x4_t const anomalies = vld1q_dup_u32(&anomaly.u32);
-    uint32x4_t const masks = vld1q_dup_u32(&mask.u32);
-    uint32x4_t matches, matches0, matches1, matches2, matches3;
-
-    sz_string_start_t text = haystack;
-    while (text + needle_length + 16 <= end) {
-
-        // Each of the following `matchesX` contains only 4 relevant bits - one per word.
-        // Each signifies a match at the given offset.
-        matches0 = vceqq_u32(vandq_u32(vreinterpretq_u32_u8(vld1q_u8((unsigned char *)text + 0)), masks), anomalies);
-        matches1 = vceqq_u32(vandq_u32(vreinterpretq_u32_u8(vld1q_u8((unsigned char *)text + 1)), masks), anomalies);
-        matches2 = vceqq_u32(vandq_u32(vreinterpretq_u32_u8(vld1q_u8((unsigned char *)text + 2)), masks), anomalies);
-        matches3 = vceqq_u32(vandq_u32(vreinterpretq_u32_u8(vld1q_u8((unsigned char *)text + 3)), masks), anomalies);
-        matches = vorrq_u32(vorrq_u32(matches0, matches1), vorrq_u32(matches2, matches3));
-
-        if (vmaxvq_u32(matches)) {
-            // Let's isolate the match from every word
-            matches0 = vandq_u32(matches0, vdupq_n_u32(0x00000001));
-            matches1 = vandq_u32(matches1, vdupq_n_u32(0x00000002));
-            matches2 = vandq_u32(matches2, vdupq_n_u32(0x00000004));
-            matches3 = vandq_u32(matches3, vdupq_n_u32(0x00000008));
-            matches = vorrq_u32(vorrq_u32(matches0, matches1), vorrq_u32(matches2, matches3));
-
-            // By now, every 32-bit word of `matches` no more than 4 set bits.
-            // Meaning that we can narrow it down to a single 16-bit word.
-            uint16x4_t matches_u16x4 = vmovn_u32(matches);
-            uint16_t matches_u16 =                       //
-                (vget_lane_u16(matches_u16x4, 0) << 0) | //
-                (vget_lane_u16(matches_u16x4, 1) << 4) | //
-                (vget_lane_u16(matches_u16x4, 2) << 8) | //
-                (vget_lane_u16(matches_u16x4, 3) << 12);
-
-            // Find the first match
-            sz_size_t first_match_offset = ctz64(matches_u16);
-            if (needle_length > 4) {
-                if (sz_equal(text + first_match_offset + 4, needle + 4, needle_length - 4)) {
-                    return text + first_match_offset;
-                }
-                else { text += first_match_offset + 1; }
-            }
-            else { return text + first_match_offset; }
-        }
-        else { text += 16; }
-    }
-
-    // Don't forget the last (up to 16+3=19) characters.
-    return sz_find_substring_swar(text, end - text, needle, needle_length);
-}
-
-#endif // Arm Neon
-
-inline static sz_size_t sz_count_char(sz_string_start_t const haystack,
-                                      sz_size_t const haystack_length,
-                                      sz_string_start_t const needle) {
-    return sz_count_char_swar(haystack, haystack_length, needle);
-}
-
-inline static sz_string_start_t sz_find_1char(sz_string_start_t const haystack,
-                                              sz_size_t const haystack_length,
-                                              sz_string_start_t const needle) {
-    return sz_find_1char_swar(haystack, haystack_length, needle);
-}
-
-inline static sz_string_start_t sz_rfind_1char(sz_string_start_t const haystack,
-                                               sz_size_t const haystack_length,
-                                               sz_string_start_t const needle) {
-    return sz_rfind_1char_swar(haystack, haystack_length, needle);
-}
-
-inline static sz_string_start_t sz_find_substring(sz_string_start_t const haystack,
-                                                  sz_size_t const haystack_length,
-                                                  sz_string_start_t const needle,
-                                                  sz_size_t const needle_length) {
-    if (haystack_length < needle_length || needle_length == 0) return NULL;
-#if defined(__ARM_NEON)
-    return sz_find_substring_neon(haystack, haystack_length, needle, needle_length);
-#elif defined(__AVX2__)
-    return sz_find_substring_avx2(haystack, haystack_length, needle, needle_length);
-#else
-    return sz_find_substring_swar(haystack, haystack_length, needle, needle_length);
-#endif
-}
-
-/**
- *  @brief  Maps any ASCII character to itself, or the lowercase variant, if available.
- */
-inline static char sz_tolower_ascii(char c) {
-    static unsigned char lowered[256] = {
-        0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  //
-        16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  //
-        32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  //
-        48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  //
-        64,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //
-        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91,  92,  93,  94,  95,  //
-        96,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //
-        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, //
-        128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, //
-        144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, //
-        160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, //
-        176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, //
-        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
-        240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, //
-        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
-        240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, //
-    };
-    return *(char *)&lowered[(int)c];
-}
-
-/**
- *  @brief  Maps any ASCII character to itself, or the uppercase variant, if available.
- */
-inline static char sz_toupper_ascii(char c) {
-    static unsigned char upped[256] = {
-        0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  //
-        16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  //
-        32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  //
-        48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  //
-        64,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //
-        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91,  92,  93,  94,  95,  //
-        96,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,  //
-        80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  123, 124, 125, 126, 127, //
-        128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, //
-        144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, //
-        160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, //
-        176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, //
-        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
-        240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, //
-        224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, //
-        240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, //
-    };
-    return *(char *)&upped[(int)c];
-}
-
-/**
- *  @brief Load a 64-bit unsigned integer from a potentially unaligned pointer.
- *
- *  @note This function uses compiler-specific attributes or keywords to
- *        ensure correct and efficient unaligned loads. It's designed to work
- *        with both MSVC and GCC/Clang.
- */
-inline static sz_u64_t sz_u64_unaligned_load(void const *ptr) {
-#ifdef _MSC_VER
-    return *((__unaligned sz_u64_t *)ptr);
-#else
-    __attribute__((aligned(1))) sz_u64_t const *uptr = (sz_u64_t const *)ptr;
-    return *uptr;
-#endif
-}
-
-/**
- *  @brief Reverse the byte order of a 64-bit unsigned integer.
- *
- *  @note This function uses compiler-specific intrinsics to achieve the
- *        byte-reversal. It's designed to work with both MSVC and GCC/Clang.
- */
-inline static sz_u64_t sz_u64_byte_reverse(sz_u64_t val) {
-#ifdef _MSC_VER
-    return _byteswap_uint64(val);
-#else
-    return __builtin_bswap64(val);
-#endif
-}
-
-/**
- *  @brief  Compute the logarithm base 2 of an integer.
- *
- *  @note If n is 0, the function returns 0 to avoid undefined behavior.
- *  @note This function uses compiler-specific intrinsics or built-ins
- *        to achieve the computation. It's designed to work with GCC/Clang and MSVC.
- */
-inline static sz_size_t sz_log2i(sz_size_t n) {
-    if (n == 0) return 0;
-
-#ifdef _WIN64
-#ifdef _MSC_VER
-    unsigned long index;
-    if (_BitScanReverse64(&index, n)) return index;
-    return 0; // This line might be redundant due to the initial check, but it's safer to include it.
-#else
-    return 63 - __builtin_clzll(n);
-#endif
-#elif defined(_WIN32)
-#ifdef _MSC_VER
-    unsigned long index;
-    if (_BitScanReverse(&index, n)) return index;
-    return 0; // Same note as above.
-#else
-    return 31 - __builtin_clz(n);
-#endif
-#else
-// Handle non-Windows platforms. You can further differentiate between 32-bit and 64-bit if needed.
-#if defined(__LP64__)
-    return 63 - __builtin_clzll(n);
-#else
-    return 31 - __builtin_clz(n);
-#endif
-#endif
-}
-
-/**
- *  @brief  Char-level lexicographic comparison of two strings.
- *          Doesn't provide major performance improvements, but helps avoid the LibC dependency.
- */
-inline static sz_bool_t sz_is_less_ascii(sz_string_start_t a,
-                                         sz_size_t const a_length,
-                                         sz_string_start_t b,
-                                         sz_size_t const b_length) {
-
-    sz_size_t min_length = (a_length < b_length) ? a_length : b_length;
-    sz_string_start_t const min_end = a + min_length;
-    while (a + 8 <= min_end && sz_u64_unaligned_load(a) == sz_u64_unaligned_load(b)) a += 8, b += 8;
-    while (a != min_end && *a == *b) a++, b++;
-    return a != min_end ? (*a < *b) : (a_length < b_length);
-}
-
-/**
- *  @brief  Char-level lexicographic comparison of two strings, insensitive to the case of ASCII symbols.
- *          Doesn't provide major performance improvements, but helps avoid the LibC dependency.
- */
-inline static sz_bool_t sz_is_less_uncased_ascii(sz_string_start_t const a,
-                                                 sz_size_t const a_length,
-                                                 sz_string_start_t const b,
-                                                 sz_size_t const b_length) {
-
-    sz_size_t min_length = (a_length < b_length) ? a_length : b_length;
-    for (sz_size_t i = 0; i < min_length; ++i) {
-        char a_lower = sz_tolower_ascii(a[i]);
-        char b_lower = sz_tolower_ascii(b[i]);
-        if (a_lower < b_lower) return 1;
-        if (a_lower > b_lower) return 0;
-    }
-    return a_length < b_length;
-}
-
-/**
- *  @brief  Helper, that swaps two 64-bit integers representing the order of elements in the sequence.
- */
-inline static void _sz_swap_order(sz_u64_t *a, sz_u64_t *b) {
-    sz_u64_t t = *a;
-    *a = *b;
-    *b = t;
-}
-
-struct sz_sequence_t;
-
-typedef sz_string_start_t (*sz_sequence_member_start_t)(struct sz_sequence_t const *, sz_size_t);
-typedef sz_size_t (*sz_sequence_member_length_t)(struct sz_sequence_t const *, sz_size_t);
-typedef sz_bool_t (*sz_sequence_predicate_t)(struct sz_sequence_t const *, sz_size_t);
-typedef sz_bool_t (*sz_sequence_comparator_t)(struct sz_sequence_t const *, sz_size_t, sz_size_t);
-typedef sz_bool_t (*sz_string_is_less_t)(sz_string_start_t, sz_size_t, sz_string_start_t, sz_size_t);
-
-typedef struct sz_sequence_t {
-    sz_u64_t *order;
-    sz_size_t count;
-    sz_sequence_member_start_t get_start;
-    sz_sequence_member_length_t get_length;
-    void const *handle;
-} sz_sequence_t;
-
-/**
- *  @brief  Similar to `std::partition`, given a predicate splits the sequence into two parts.
- *          The algorithm is unstable, meaning that elements may change relative order, as long
- *          as they are in the right partition. This is the simpler algorithm for partitioning.
- */
-inline static sz_size_t sz_partition(sz_sequence_t *sequence, sz_sequence_predicate_t predicate) {
-
-    sz_size_t matches = 0;
-    while (matches != sequence->count && predicate(sequence, sequence->order[matches])) ++matches;
-
-    for (sz_size_t i = matches + 1; i < sequence->count; ++i)
-        if (predicate(sequence, sequence->order[i]))
-            _sz_swap_order(sequence->order + i, sequence->order + matches), ++matches;
-
-    return matches;
-}
-
-/**
- *  @brief  Inplace `std::set_union` for two consecutive chunks forming the same continuous `sequence`.
- *
- *  @param partition The number of elements in the first sub-sequence in `sequence`.
- *  @param less Comparison function, to determine the lexicographic ordering.
- */
-inline static void sz_merge(sz_sequence_t *sequence, sz_size_t partition, sz_sequence_comparator_t less) {
-
-    sz_size_t start_b = partition + 1;
-
-    // If the direct merge is already sorted
-    if (!less(sequence, sequence->order[start_b], sequence->order[partition])) return;
-
-    sz_size_t start_a = 0;
-    while (start_a <= partition && start_b <= sequence->count) {
-
-        // If element 1 is in right place
-        if (!less(sequence, sequence->order[start_b], sequence->order[start_a])) { start_a++; }
-        else {
-            sz_size_t value = sequence->order[start_b];
-            sz_size_t index = start_b;
-
-            // Shift all the elements between element 1
-            // element 2, right by 1.
-            while (index != start_a) { sequence->order[index] = sequence->order[index - 1], index--; }
-            sequence->order[start_a] = value;
-
-            // Update all the pointers
-            start_a++;
-            partition++;
-            start_b++;
-        }
-    }
-}
-
-inline static void sz_sort_insertion(sz_sequence_t *sequence, sz_sequence_comparator_t less) {
-    sz_u64_t *keys = sequence->order;
-    sz_size_t keys_count = sequence->count;
-    for (sz_size_t i = 1; i < keys_count; i++) {
-        sz_u64_t i_key = keys[i];
-        sz_size_t j = i;
-        for (; j > 0 && less(sequence, i_key, keys[j - 1]); --j) keys[j] = keys[j - 1];
-        keys[j] = i_key;
-    }
-}
-
-inline static void _sz_sift_down(
-    sz_sequence_t *sequence, sz_sequence_comparator_t less, sz_u64_t *order, sz_size_t start, sz_size_t end) {
-    sz_size_t root = start;
-    while (2 * root + 1 <= end) {
-        sz_size_t child = 2 * root + 1;
-        if (child + 1 <= end && less(sequence, order[child], order[child + 1])) { child++; }
-        if (!less(sequence, order[root], order[child])) { return; }
-        _sz_swap_order(order + root, order + child);
-        root = child;
-    }
-}
-
-inline static void _sz_heapify(sz_sequence_t *sequence,
-                               sz_sequence_comparator_t less,
-                               sz_u64_t *order,
-                               sz_size_t count) {
-    sz_size_t start = (count - 2) / 2;
-    while (1) {
-        _sz_sift_down(sequence, less, order, start, count - 1);
-        if (start == 0) return;
-        start--;
-    }
-}
-
-inline static void _sz_heapsort(sz_sequence_t *sequence,
-                                sz_sequence_comparator_t less,
-                                sz_size_t first,
-                                sz_size_t last) {
-    sz_u64_t *order = sequence->order;
-    sz_size_t count = last - first;
-    _sz_heapify(sequence, less, order + first, count);
-    sz_size_t end = count - 1;
-    while (end > 0) {
-        _sz_swap_order(order + first, order + first + end);
-        end--;
-        _sz_sift_down(sequence, less, order + first, 0, end);
-    }
-}
-
-inline static void _sz_introsort(
-    sz_sequence_t *sequence, sz_sequence_comparator_t less, sz_size_t first, sz_size_t last, sz_size_t depth) {
-
-    sz_size_t length = last - first;
-    switch (length) {
-    case 0:
-    case 1: return;
-    case 2:
-        if (less(sequence, sequence->order[first + 1], sequence->order[first]))
-            _sz_swap_order(&sequence->order[first], &sequence->order[first + 1]);
-        return;
-    case 3: {
-        sz_u64_t a = sequence->order[first];
-        sz_u64_t b = sequence->order[first + 1];
-        sz_u64_t c = sequence->order[first + 2];
-        if (less(sequence, b, a)) _sz_swap_order(&a, &b);
-        if (less(sequence, c, b)) _sz_swap_order(&c, &b);
-        if (less(sequence, b, a)) _sz_swap_order(&a, &b);
-        sequence->order[first] = a;
-        sequence->order[first + 1] = b;
-        sequence->order[first + 2] = c;
-        return;
-    }
-    }
-    // Until a certain length, the quadratic-complexity insertion-sort is fine
-    if (length <= 16) {
-        sz_sequence_t sub_seq = *sequence;
-        sub_seq.order += first;
-        sub_seq.count = length;
-        sz_sort_insertion(&sub_seq, less);
-        return;
-    }
-
-    // Fallback to N-logN-complexity heap-sort
-    if (depth == 0) {
-        _sz_heapsort(sequence, less, first, last);
-        return;
-    }
-
-    --depth;
-
-    // Median-of-three logic to choose pivot
-    sz_size_t median = first + length / 2;
-    if (less(sequence, sequence->order[median], sequence->order[first]))
-        _sz_swap_order(&sequence->order[first], &sequence->order[median]);
-    if (less(sequence, sequence->order[last - 1], sequence->order[first]))
-        _sz_swap_order(&sequence->order[first], &sequence->order[last - 1]);
-    if (less(sequence, sequence->order[median], sequence->order[last - 1]))
-        _sz_swap_order(&sequence->order[median], &sequence->order[last - 1]);
-
-    // Partition using the median-of-three as the pivot
-    sz_u64_t pivot = sequence->order[median];
-    sz_size_t left = first;
-    sz_size_t right = last - 1;
-    while (1) {
-        while (less(sequence, sequence->order[left], pivot)) left++;
-        while (less(sequence, pivot, sequence->order[right])) right--;
-        if (left >= right) break;
-        _sz_swap_order(&sequence->order[left], &sequence->order[right]);
-        left++;
-        right--;
-    }
-
-    // Recursively sort the partitions
-    _sz_introsort(sequence, less, first, left, depth);
-    _sz_introsort(sequence, less, right + 1, last, depth);
-}
-
-inline static void sz_sort_introsort(sz_sequence_t *sequence, sz_sequence_comparator_t less) {
-    sz_size_t depth_limit = 2 * sz_log2i(sequence->count);
-    _sz_introsort(sequence, less, 0, sequence->count, depth_limit);
-}
-
-inline static void _sz_sort_recursion( //
-    sz_sequence_t *sequence,
-    sz_size_t bit_idx,
-    sz_size_t bit_max,
-    sz_sequence_comparator_t comparator,
-    sz_size_t partial_order_length) {
-
-    if (!sequence->count) return;
-
-    // Partition a range of integers according to a specific bit value
-    sz_size_t split = 0;
-    {
-        sz_u64_t mask = (1ull << 63) >> bit_idx;
-        while (split != sequence->count && !(sequence->order[split] & mask)) ++split;
-        for (sz_size_t i = split + 1; i < sequence->count; ++i)
-            if (!(sequence->order[i] & mask)) _sz_swap_order(sequence->order + i, sequence->order + split), ++split;
-    }
-
-    // Go down recursively
-    if (bit_idx < bit_max) {
-        sz_sequence_t a = *sequence;
-        a.count = split;
-        _sz_sort_recursion(&a, bit_idx + 1, bit_max, comparator, partial_order_length);
-
-        sz_sequence_t b = *sequence;
-        b.order += split;
-        b.count -= split;
-        _sz_sort_recursion(&b, bit_idx + 1, bit_max, comparator, partial_order_length);
-    }
-    // Reached the end of recursion
-    else {
-        // Discard the prefixes
-        sz_u32_t *order_half_words = (sz_u32_t *)sequence->order;
-        for (sz_size_t i = 0; i != sequence->count; ++i) { order_half_words[i * 2 + 1] = 0; }
-
-        sz_sequence_t a = *sequence;
-        a.count = split;
-        sz_sort_introsort(&a, comparator);
-
-        sz_sequence_t b = *sequence;
-        b.order += split;
-        b.count -= split;
-        sz_sort_introsort(&b, comparator);
-    }
-}
-
-inline static sz_bool_t _sz_sort_compare_less_ascii(sz_sequence_t *sequence, sz_size_t i_key, sz_size_t j_key) {
-    sz_string_start_t i_str = sequence->get_start(sequence, i_key);
-    sz_size_t i_len = sequence->get_length(sequence, i_key);
-    sz_string_start_t j_str = sequence->get_start(sequence, j_key);
-    sz_size_t j_len = sequence->get_length(sequence, j_key);
-    return sz_is_less_ascii(i_str, i_len, j_str, j_len);
-}
-
-inline static sz_bool_t _sz_sort_compare_less_uncased_ascii(sz_sequence_t *sequence, sz_size_t i_key, sz_size_t j_key) {
-    sz_string_start_t i_str = sequence->get_start(sequence, i_key);
-    sz_size_t i_len = sequence->get_length(sequence, i_key);
-    sz_string_start_t j_str = sequence->get_start(sequence, j_key);
-    sz_size_t j_len = sequence->get_length(sequence, j_key);
-    return sz_is_less_uncased_ascii(i_str, i_len, j_str, j_len);
-}
-
-typedef struct sz_sort_config_t {
-    sz_bool_t case_insensitive;
-    sz_size_t partial_order_length;
-} sz_sort_config_t;
-
-/**
- *  @brief  Sorting algorithm, combining Radix Sort for the first 32 bits of every word
- *          and a follow-up by a more conventional sorting procedure on equally prefixed parts.
- */
-inline static void sz_sort(sz_sequence_t *sequence, sz_sort_config_t const *config) {
-
-    sz_bool_t case_insensitive = config && config->case_insensitive;
-    sz_size_t partial_order_length =
-        config && config->partial_order_length ? config->partial_order_length : sequence->count;
-
-    // Export up to 4 bytes into the `sequence` bits themselves
-    for (sz_size_t i = 0; i != sequence->count; ++i) {
-        sz_string_start_t begin = sequence->get_start(sequence, sequence->order[i]);
-        sz_size_t length = sequence->get_length(sequence, sequence->order[i]);
-        length = length > 4ull ? 4ull : length;
-        char *prefix = (char *)&sequence->order[i];
-        for (sz_size_t j = 0; j != length; ++j) prefix[7 - j] = begin[j];
-        if (case_insensitive) {
-            prefix[0] = sz_tolower_ascii(prefix[0]);
-            prefix[1] = sz_tolower_ascii(prefix[1]);
-            prefix[2] = sz_tolower_ascii(prefix[2]);
-            prefix[3] = sz_tolower_ascii(prefix[3]);
-        }
-    }
-
-    sz_sequence_comparator_t comparator = (sz_sequence_comparator_t)_sz_sort_compare_less_ascii;
-    if (case_insensitive) comparator = (sz_sequence_comparator_t)_sz_sort_compare_less_uncased_ascii;
-
-    // Perform optionally-parallel radix sort on them
-    _sz_sort_recursion(sequence, 0, 32, comparator, partial_order_length);
-}
-
-/**
- *  @return Amount of temporary memory (in bytes) needed to efficiently compute
- *          the Levenstein distance between two strings of given size.
- */
-inline static sz_size_t sz_levenstein_memory_needed(sz_size_t _, sz_size_t b_length) { return b_length + b_length + 2; }
-
-/**
- *  @brief  Auxiliary function, that computes the minimum of three values.
- */
-inline static levenstein_distance_t _sz_levenstein_minimum( //
-    levenstein_distance_t const a,
-    levenstein_distance_t const b,
-    levenstein_distance_t const c) {
-
-    return (a < b ? (a < c ? a : c) : (b < c ? b : c));
-}
-
-/**
- *  @brief  Levenshtein String Similarity function, implemented with linear memory consumption.
- *          It accepts an upper bound on the possible error. Quadratic complexity in time, linear in space.
- */
-inline static levenstein_distance_t sz_levenstein( //
-    sz_string_start_t const a,
-    sz_size_t const a_length,
-    sz_string_start_t const b,
-    sz_size_t const b_length,
-    levenstein_distance_t const bound,
-    void *buffer) {
-
-    // If one of the strings is empty - the edit distance is equal to the length of the other one
-    if (a_length == 0) return b_length <= bound ? b_length : bound;
-    if (b_length == 0) return a_length <= bound ? a_length : bound;
-
-    // If the difference in length is beyond the `bound`, there is no need to check at all
-    if (a_length > b_length) {
-        if (a_length - b_length > bound) return bound + 1;
-    }
-    else {
-        if (b_length - a_length > bound) return bound + 1;
-    }
-
-    levenstein_distance_t *previous_distances = (levenstein_distance_t *)buffer;
-    levenstein_distance_t *current_distances = previous_distances + b_length + 1;
-
-    for (sz_size_t idx_b = 0; idx_b != (b_length + 1); ++idx_b) previous_distances[idx_b] = idx_b;
-
-    for (sz_size_t idx_a = 0; idx_a != a_length; ++idx_a) {
-        current_distances[0] = idx_a + 1;
-
-        // Initialize min_distance with a value greater than bound
-        levenstein_distance_t min_distance = bound;
-
-        for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
-            levenstein_distance_t cost_deletion = previous_distances[idx_b + 1] + 1;
-            levenstein_distance_t cost_insertion = current_distances[idx_b] + 1;
-            levenstein_distance_t cost_substitution = previous_distances[idx_b] + (a[idx_a] != b[idx_b]);
-            current_distances[idx_b + 1] = _sz_levenstein_minimum(cost_deletion, cost_insertion, cost_substitution);
-
-            // Keep track of the minimum distance seen so far in this row
-            if (current_distances[idx_b + 1] < min_distance) { min_distance = current_distances[idx_b + 1]; }
-        }
-
-        // If the minimum distance in this row exceeded the bound, return early
-        if (min_distance > bound) return bound;
-
-        // Swap previous_distances and current_distances pointers
-        levenstein_distance_t *temp = previous_distances;
-        previous_distances = current_distances;
-        current_distances = temp;
-    }
-
-    return previous_distances[b_length] <= bound ? previous_distances[b_length] : bound;
-}
-
-inline static sz_u32_t sz_hash_crc32_swar(sz_string_start_t start, sz_size_t length) {
-    /*
-     * The following CRC lookup table was generated automagically using the
-     * following model parameters:
-     *
-     * Generator Polynomial = ................. 0x1EDC6F41
-     * Generator Polynomial Length = .......... 32 bits
-     * Reflected Bits = ....................... TRUE
-     * Table Generation Offset = .............. 32 bits
-     * Number of Slices = ..................... 8 slices
-     * Slice Lengths = ........................ 8 8 8 8 8 8 8 8
-     */
-
-    static sz_u32_t const table[256] = {
-        0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, //
-        0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, //
-        0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, //
-        0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, //
-        0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, //
-        0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, //
-        0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, //
-        0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, //
-        0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, //
-        0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, //
-        0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, //
-        0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, //
-        0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, //
-        0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, //
-        0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, //
-        0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, //
-        0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, //
-        0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, //
-        0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, //
-        0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, //
-        0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, //
-        0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, //
-        0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, //
-        0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, //
-        0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, //
-        0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, //
-        0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, //
-        0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, //
-        0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, //
-        0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, //
-        0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, //
-        0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351  //
-    };
-
-    sz_u32_t crc = 0xFFFFFFFF;
-    for (sz_string_start_t const end = start + length; start != end; ++start)
-        crc = (crc >> 8) ^ table[(crc ^ (sz_u32_t)*start) & 0xff];
-    return crc ^ 0xFFFFFFFF;
-}
-
-#if defined(__ARM_FEATURE_CRC32)
-inline static sz_u32_t sz_hash_crc32_arm(sz_string_start_t start, sz_size_t length) {
-    sz_u32_t crc = 0xFFFFFFFF;
-    sz_string_start_t const end = start + length;
-
-    // Align the input to the word boundary
-    while (((unsigned long)start & 7ull) && start != end) { crc = __crc32cb(crc, *start), start++; }
-
-    // Process the body 8 bytes at a time
-    while (start + 8 <= end) { crc = __crc32cd(crc, *(unsigned long long *)start), start += 8; }
-
-    // Process the tail bytes
-    if (start + 4 <= end) { crc = __crc32cw(crc, *(unsigned int *)start), start += 4; }
-    if (start + 2 <= end) { crc = __crc32ch(crc, *(unsigned short *)start), start += 2; }
-    if (start < end) { crc = __crc32cb(crc, *start); }
-    return crc ^ 0xFFFFFFFF;
-}
-#endif
-
-#if defined(__SSE4_2__)
-inline static sz_u32_t sz_hash_crc32_sse(sz_string_start_t start, sz_size_t length) {
-    sz_u32_t crc = 0xFFFFFFFF;
-    sz_string_start_t const end = start + length;
-
-    // Align the input to the word boundary
-    while (((unsigned long)start & 7ull) && start != end) { crc = _mm_crc32_u8(crc, *start), start++; }
-
-    // Process the body 8 bytes at a time
-    while (start + 8 <= end) { crc = (sz_u32_t)_mm_crc32_u64(crc, *(unsigned long long *)start), start += 8; }
-
-    // Process the tail bytes
-    if (start + 4 <= end) { crc = _mm_crc32_u32(crc, *(unsigned int *)start), start += 4; }
-    if (start + 2 <= end) { crc = _mm_crc32_u16(crc, *(unsigned short *)start), start += 2; }
-    if (start < end) { crc = _mm_crc32_u8(crc, *start); }
-    return crc ^ 0xFFFFFFFF;
-}
-#endif
-
-/**
- *  @brief  Hashes provided string using hardware-accelerated CRC32 instructions.
- */
-inline static sz_u32_t sz_hash_crc32(sz_string_start_t start, sz_size_t length) {
-#if defined(__ARM_FEATURE_CRC32)
-    return sz_hash_crc32_arm(start, length);
-#elif defined(__SSE4_2__)
-    return sz_hash_crc32_sse(start, length);
-#else
-    return sz_hash_crc32_swar(start, length);
-#endif
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#undef popcount64
-#undef ctz64
-#undef clz64
-
-#endif // STRINGZILLA_H_
diff --git a/swift/StringProtocol+StringZilla.swift b/swift/StringProtocol+StringZilla.swift
new file mode 100644
index 00000000..0f7b36bc
--- /dev/null
+++ b/swift/StringProtocol+StringZilla.swift
@@ -0,0 +1,280 @@
+//
+//  StringProtocol+StringZilla.swift
+//
+//  Created by Ash Vardanian on 18/1/24.
+//  Extension of StringProtocol to interface with StringZilla functionalities.
+//
+//  Docs:
+//  - Accessing immutable UTF8-range:
+//    https://developer.apple.com/documentation/swift/string/utf8view
+//
+//  More reading materials:
+//  - String’s ABI and UTF-8. Nov 2018
+//    https://forums.swift.org/t/string-s-abi-and-utf-8/17676
+//  - Stable pointer into a C string without copying it? Aug 2021
+//    https://forums.swift.org/t/stable-pointer-into-a-c-string-without-copying-it/51244/1
+
+import StringZillaC
+
+// We need to link the standard libraries.
+#if os(Linux)
+import Glibc
+#else
+import Darwin.C
+#endif
+
+/// Protocol defining a single-byte data type.
+fileprivate protocol SingleByte {}
+
+extension UInt8: SingleByte {}
+extension Int8: SingleByte {} // This would match `CChar` as well.
+
+@usableFromInline
+enum StringZillaError: Error {
+    case contiguousStorageUnavailable
+    case memoryAllocationFailed
+    
+    var localizedDescription: String {
+        switch self {
+        case .contiguousStorageUnavailable:
+            return "Contiguous storage for the sequence is unavailable."
+        case .memoryAllocationFailed:
+            return "Memory allocation failed."
+        }
+    }
+}
+
+/// Protocol defining the interface for StringZilla-compatible byte-spans.
+///
+/// # Discussion:
+/// The Swift documentation is extremely vague about the actual memory layout of a String
+/// and the cost of obtaining the underlying UTF8 representation or any other raw pointers.
+/// https://developer.apple.com/documentation/swift/stringprotocol/withcstring(_:)
+/// https://developer.apple.com/documentation/swift/stringprotocol/withcstring(encodedas:_:)
+/// https://developer.apple.com/documentation/swift/stringprotocol/data(using:allowlossyconversion:)
+public protocol StringZillaViewable: Collection {
+    /// A type that represents a position in the collection.
+    /// 
+    /// Executes a closure with a pointer to the string's UTF8 C representation and its length.
+    ///
+    /// - Parameters:
+    ///   - body: A closure that takes a pointer to a C string and its length.
+    /// - Throws: Can throw an error.
+    /// - Returns: Returns a value of type R, which is the result of the closure.
+    func withStringZillaScope<R>(_ body: (sz_cptr_t, sz_size_t) throws -> R) rethrows -> R
+    
+    /// Calculates the offset index for a given byte pointer relative to a start pointer.
+    ///
+    /// - Parameters:
+    ///   - bytePointer: A pointer to the byte for which the offset is calculated.
+    ///   - startPointer: The starting pointer for the calculation, previously obtained from `szScope`.
+    /// - Returns: The calculated index offset.
+    func stringZillaByteOffset(forByte bytePointer: sz_cptr_t, after startPointer: sz_cptr_t) -> Index
+}
+
+extension String: StringZillaViewable {
+    public typealias Index = String.Index
+    
+    @_transparent
+    public func withStringZillaScope<R>(_ body: (sz_cptr_t, sz_size_t) throws -> R) rethrows -> R {
+        let cLength = sz_size_t(utf8.count)
+        return try self.withCString { cString in
+            try body(cString, cLength)
+        }
+    }
+    
+    @_transparent
+    public func stringZillaByteOffset(forByte bytePointer: sz_cptr_t, after startPointer: sz_cptr_t) -> Index {
+        self.utf8.index(self.utf8.startIndex, offsetBy: bytePointer - startPointer)
+    }
+}
+
+extension Substring.UTF8View: StringZillaViewable {
+    public typealias Index = Substring.UTF8View.Index
+    
+    /// Executes a closure with a pointer to the UTF8View's contiguous storage of single-byte elements (UTF-8 code units).
+    /// - Parameters:
+    ///   - body: A closure that takes a pointer to the contiguous storage and its size.
+    /// - Throws: An error if the storage is not contiguous.
+    @_transparent
+    public func withStringZillaScope<R>(_ body: (sz_cptr_t, sz_size_t) throws -> R) rethrows -> R {
+        return try withContiguousStorageIfAvailable { bufferPointer -> R in
+            let cLength = sz_size_t(bufferPointer.count)
+            let cString = UnsafeRawPointer(bufferPointer.baseAddress!).assumingMemoryBound(to: CChar.self)
+            return try body(cString, cLength)
+        } ?? {
+            throw StringZillaError.contiguousStorageUnavailable
+        }()
+    }
+    
+    /// Calculates the offset index for a given byte pointer relative to a start pointer.
+    /// - Parameters:
+    ///   - bytePointer: A pointer to the byte for which the offset is calculated.
+    ///   - startPointer: The starting pointer for the calculation, previously obtained from `szScope`.
+    /// - Returns: The calculated index offset.
+    @_transparent
+    public func stringZillaByteOffset(forByte bytePointer: sz_cptr_t, after startPointer: sz_cptr_t) -> Index {
+        return self.index(self.startIndex, offsetBy: bytePointer - startPointer)
+    }
+}
+
+extension String.UTF8View: StringZillaViewable {
+    public typealias Index = String.UTF8View.Index
+    
+    /// Executes a closure with a pointer to the UTF8View's contiguous storage of single-byte elements (UTF-8 code units).
+    /// - Parameters:
+    ///   - body: A closure that takes a pointer to the contiguous storage and its size.
+    /// - Throws: An error if the storage is not contiguous.
+    public func withStringZillaScope<R>(_ body: (sz_cptr_t, sz_size_t) throws -> R) rethrows -> R {
+        return try withContiguousStorageIfAvailable { bufferPointer -> R in
+            let cLength = sz_size_t(bufferPointer.count)
+            let cString = UnsafeRawPointer(bufferPointer.baseAddress!).assumingMemoryBound(to: CChar.self)
+            return try body(cString, cLength)
+        } ?? {
+            throw StringZillaError.contiguousStorageUnavailable
+        }()
+    }
+    
+    /// Calculates the offset index for a given byte pointer relative to a start pointer.
+    /// - Parameters:
+    ///   - bytePointer: A pointer to the byte for which the offset is calculated.
+    ///   - startPointer: The starting pointer for the calculation, previously obtained from `szScope`.
+    /// - Returns: The calculated index offset.
+    public func stringZillaByteOffset(forByte bytePointer: sz_cptr_t, after startPointer: sz_cptr_t) -> Index {
+        return self.index(self.startIndex, offsetBy: bytePointer - startPointer)
+    }
+}
+
+public extension StringZillaViewable {
+    
+    /// Finds the first occurrence of the specified substring within the receiver.
+    /// - Parameter needle: The substring to search for.
+    /// - Returns: The index of the found occurrence, or `nil` if not found.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func findFirst<S: StringZillaViewable>(substring needle: S) -> Index? {
+        var result: Index?
+        withStringZillaScope { hPointer, hLength in
+            needle.withStringZillaScope { nPointer, nLength in
+                if let matchPointer = sz_find(hPointer, hLength, nPointer, nLength) {
+                    result = self.stringZillaByteOffset(forByte: matchPointer, after: hPointer)
+                }
+            }
+        }
+        return result
+    }
+    
+    /// Finds the last occurrence of the specified substring within the receiver.
+    /// - Parameter needle: The substring to search for.
+    /// - Returns: The index of the found occurrence, or `nil` if not found.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func findLast<S: StringZillaViewable>(substring needle: S) -> Index? {
+        var result: Index?
+        withStringZillaScope { hPointer, hLength in
+            needle.withStringZillaScope { nPointer, nLength in
+                if let matchPointer = sz_rfind(hPointer, hLength, nPointer, nLength) {
+                    result = self.stringZillaByteOffset(forByte: matchPointer, after: hPointer)
+                }
+            }
+        }
+        return result
+    }
+    
+    /// Finds the first occurrence of the specified character-set members within the receiver.
+    /// - Parameter characters: A string-like collection of characters to match.
+    /// - Returns: The index of the found occurrence, or `nil` if not found.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func findFirst<S: StringZillaViewable>(characterFrom characters: S) -> Index? {
+        var result: Index?
+        withStringZillaScope { hPointer, hLength in
+            characters.withStringZillaScope { nPointer, nLength in
+                if let matchPointer = sz_find_char_from(hPointer, hLength, nPointer, nLength) {
+                    result = self.stringZillaByteOffset(forByte: matchPointer, after: hPointer)
+                }
+            }
+        }
+        return result
+    }
+    
+    /// Finds the last occurrence of the specified character-set members within the receiver.
+    /// - Parameter characters: A string-like collection of characters to match.
+    /// - Returns: The index of the found occurrence, or `nil` if not found.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func findLast<S: StringZillaViewable>(characterFrom characters: S) -> Index? {
+        var result: Index?
+        withStringZillaScope { hPointer, hLength in
+            characters.withStringZillaScope { nPointer, nLength in
+                if let matchPointer = sz_rfind_char_from(hPointer, hLength, nPointer, nLength) {
+                    result = self.stringZillaByteOffset(forByte: matchPointer, after: hPointer)
+                }
+            }
+        }
+        return result
+    }
+    
+    /// Finds the first occurrence of a character outside of the the given character-set within the receiver.
+    /// - Parameter characters: A string-like collection of characters to exclude.
+    /// - Returns: The index of the found occurrence, or `nil` if not found.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func findFirst<S: StringZillaViewable>(characterNotFrom characters: S) -> Index? {
+        var result: Index?
+        withStringZillaScope { hPointer, hLength in
+            characters.withStringZillaScope { nPointer, nLength in
+                if let matchPointer = sz_find_char_not_from(hPointer, hLength, nPointer, nLength) {
+                    result = self.stringZillaByteOffset(forByte: matchPointer, after: hPointer)
+                }
+            }
+        }
+        return result
+    }
+    
+    /// Finds the last occurrence of a character outside of the the given character-set within the receiver.
+    /// - Parameter characters: A string-like collection of characters to exclude.
+    /// - Returns: The index of the found occurrence, or `nil` if not found.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func findLast<S: StringZillaViewable>(characterNotFrom characters: S) -> Index? {
+        var result: Index?
+        withStringZillaScope { hPointer, hLength in
+            characters.withStringZillaScope { nPointer, nLength in
+                if let matchPointer = sz_rfind_char_not_from(hPointer, hLength, nPointer, nLength) {
+                    result = self.stringZillaByteOffset(forByte: matchPointer, after: hPointer)
+                }
+            }
+        }
+        return result
+    }
+    
+    /// Computes the Levenshtein edit distance between this and another string.
+    /// - Parameter other: A string-like collection of characters to exclude.
+    /// - Returns: The edit distance, as an unsigned integer.
+    /// - Throws: If a memory allocation error has happened.
+    @_specialize(where Self == String, S == String)
+    @_specialize(where Self == String.UTF8View, S == String.UTF8View)
+    func editDistance<S: StringZillaViewable>(from other: S, bound: UInt64 = 0) throws -> UInt64? {
+        var result: UInt64?
+        
+        // Use a do-catch block to handle potential errors
+        do {
+            try withStringZillaScope { hPointer, hLength in
+                try other.withStringZillaScope { nPointer, nLength in
+                    result = UInt64(sz_edit_distance(hPointer, hLength, nPointer, nLength, sz_size_t(bound), nil))
+                    if result == SZ_SIZE_MAX {
+                        result = nil
+                        throw StringZillaError.memoryAllocationFailed
+                    }
+                }
+            }
+        } catch {
+            // Handle or rethrow the error
+            throw error
+        }
+        
+        return result
+    }
+    
+}
diff --git a/swift/Test.swift b/swift/Test.swift
new file mode 100644
index 00000000..908c0c3c
--- /dev/null
+++ b/swift/Test.swift
@@ -0,0 +1,60 @@
+//
+//  Test.swift
+//
+//
+//  Created by Ash Vardanian on 18/1/24.
+//
+
+import XCTest
+@testable import StringZilla
+
+class StringZillaTests: XCTestCase {
+    
+    var testString: String!
+    
+    override func setUp() {
+        super.setUp()
+        testString = "Hello, world! Welcome to StringZilla. 👋"
+        XCTAssertEqual(testString.count, 39)
+        XCTAssertEqual(testString.utf8.count, 42)
+    }
+    
+    func testFindFirstSubstring() {
+        let index = testString.findFirst(substring: "world")!
+        XCTAssertEqual(testString[index...], "world! Welcome to StringZilla. 👋")
+    }
+    
+    func testFindLastSubstring() {
+        let index = testString.findLast(substring: "o")!
+        XCTAssertEqual(testString[index...], "o StringZilla. 👋")
+    }
+    
+    func testFindFirstCharacterFromSet() {
+        let index = testString.findFirst(characterFrom: "aeiou")!
+        XCTAssertEqual(testString[index...], "ello, world! Welcome to StringZilla. 👋")
+    }
+    
+    func testFindLastCharacterFromSet() {
+        let index = testString.findLast(characterFrom: "aeiou")!
+        XCTAssertEqual(testString[index...], "a. 👋")
+    }
+    
+    func testFindFirstCharacterNotFromSet() {
+        let index = testString.findFirst(characterNotFrom: "aeiou")!
+        XCTAssertEqual(testString[index...], "Hello, world! Welcome to StringZilla. 👋")
+    }
+
+    // TODO: This fails!
+    // func testFindLastCharacterNotFromSet() {
+    //     let index = testString.findLast(characterNotFrom: "aeiou")!
+    //     XCTAssertEqual(testString.distance(from: testString.startIndex, to: index), 38)
+    //     XCTAssertEqual(testString[index...], "👋")
+    // }
+    
+    func testEditDistance() {
+        let otherString = "Hello, world!"
+        let distance = try? testString.editDistance(from: otherString) // Using try?
+        XCTAssertNotNil(distance)
+        XCTAssertEqual(distance, 29)
+    }
+}


Test patch:
diff --git a/scripts/test.c b/scripts/test.c
deleted file mode 100644
index b39fd982..00000000
--- a/scripts/test.c
+++ /dev/null
@@ -1,59 +0,0 @@
-#include <assert.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include <stringzilla.h>
-
-#define MAX_LENGTH 300
-#define MIN_LENGTH 3
-#define ASCII_LOWERCASE "abcdefghijklmnopqrstuvwxyz"
-#define VARIABILITY 25
-
-// Utility function to populate random string in a buffer
-void populate_random_string(char *buffer, int length, int variability) {
-    for (int i = 0; i < length; i++) { buffer[i] = ASCII_LOWERCASE[rand() % variability]; }
-    buffer[length] = '\0';
-}
-
-// Test function for sz_find_substring
-void test_sz_find_substring() {
-    char buffer[MAX_LENGTH + 1];
-    char pattern[6]; // Maximum length of 5 + 1 for '\0'
-
-    for (int length = MIN_LENGTH; length < MAX_LENGTH; length++) {
-        for (int variability = 1; variability < VARIABILITY; variability++) {
-            populate_random_string(buffer, length, variability);
-
-            sz_string_view_t haystack;
-            haystack.start = buffer;
-            haystack.length = length;
-
-            int pattern_length = rand() % 5 + 1;
-            populate_random_string(pattern, pattern_length, variability);
-
-            sz_string_view_t needle;
-            needle.start = pattern;
-            needle.length = pattern_length;
-
-            // Comparing the result of your function with the standard library function.
-            sz_string_start_t result_libc = strstr(buffer, pattern);
-            sz_string_start_t result_stringzilla =
-                sz_find_substring(haystack.start, haystack.length, needle.start, needle.length);
-
-            assert(((result_libc == NULL) ^ (result_stringzilla == NULL)) && "Test failed for sz_find_substring");
-        }
-    }
-}
-
-int main() {
-    srand((unsigned int)time(NULL));
-
-    test_sz_find_substring();
-    // Add calls to other test functions as you implement them
-
-    printf("All tests passed!\n");
-    return 0;
-}
diff --git a/scripts/test.cpp b/scripts/test.cpp
index b61b7d40..40fd3843 100644
--- a/scripts/test.cpp
+++ b/scripts/test.cpp
@@ -1,346 +1,1261 @@
-#include <algorithm>
-#include <chrono>
-#include <cstring>
-#include <fstream>
-#include <iostream>
-#include <limits>
-#include <numeric>
-#include <string>
-#include <strstream>
-#include <vector>
-
-#include <stringzilla.h>
-
-using strings_t = std::vector<std::string>;
-using idx_t = sz_size_t;
-using permute_t = std::vector<sz_u64_t>;
-
-#pragma region - C callbacks
-
-static char const *get_start(sz_sequence_t const *array_c, sz_size_t i) {
-    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
-    return array[i].c_str();
-}
+#undef NDEBUG      // Enable all assertions
+#include <cassert> // assertions
+
+#include <sanitizer/asan_interface.h> // ASAN
+
+#include <algorithm> // `std::transform`
+#include <cstdio>    // `std::printf`
+#include <cstring>   // `std::memcpy`
+#include <iterator>  // `std::distance`
+#include <memory>    // `std::allocator`
+#include <random>    // `std::random_device`
+#include <sstream>   // `std::ostringstream`
+#include <vector>    // `std::vector`
+
+// Overload the following with caution.
+// Those parameters must never be explicitly set during releases,
+// but they come handy during development, if you want to validate
+// different ISA-specific implementations.
+// #define SZ_USE_X86_AVX2 0
+// #define SZ_USE_X86_AVX512 0
+// #define SZ_USE_ARM_NEON 0
+// #define SZ_USE_ARM_SVE 0
+#define SZ_DEBUG 1 // Enforce aggressive logging for this unit.
+
+#include <string>                      // Baseline
+#include <string_view>                 // Baseline
+#include <stringzilla/stringzilla.hpp> // Contender
+
+#include <test.hpp> // `levenshtein_baseline`
+
+namespace sz = ashvardanian::stringzilla;
+using namespace sz::scripts;
+using sz::literals::operator""_sz;
+
+/**
+ *  @brief  Several string processing operations rely on computing integer logarithms.
+ *          Failures in such operations will result in wrong `resize` outcomes and heap corruption.
+ */
+static void test_arithmetical_utilities() {
+
+    assert(sz_u64_clz(0x0000000000000001ull) == 63);
+    assert(sz_u64_clz(0x0000000000000002ull) == 62);
+    assert(sz_u64_clz(0x0000000000000003ull) == 62);
+    assert(sz_u64_clz(0x0000000000000004ull) == 61);
+    assert(sz_u64_clz(0x0000000000000007ull) == 61);
+    assert(sz_u64_clz(0x8000000000000001ull) == 0);
+    assert(sz_u64_clz(0xffffffffffffffffull) == 0);
+    assert(sz_u64_clz(0x4000000000000000ull) == 1);
+
+    assert(sz_size_log2i_nonzero(1) == 0);
+    assert(sz_size_log2i_nonzero(2) == 1);
+    assert(sz_size_log2i_nonzero(3) == 1);
+
+    assert(sz_size_log2i_nonzero(4) == 2);
+    assert(sz_size_log2i_nonzero(5) == 2);
+    assert(sz_size_log2i_nonzero(7) == 2);
+
+    assert(sz_size_log2i_nonzero(8) == 3);
+    assert(sz_size_log2i_nonzero(9) == 3);
 
-static sz_size_t get_length(sz_sequence_t const *array_c, sz_size_t i) {
-    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
-    return array[i].size();
+    assert(sz_size_bit_ceil(0) == 0);
+    assert(sz_size_bit_ceil(1) == 1);
+
+    assert(sz_size_bit_ceil(2) == 2);
+    assert(sz_size_bit_ceil(3) == 4);
+    assert(sz_size_bit_ceil(4) == 4);
+
+    assert(sz_size_bit_ceil(77) == 128);
+    assert(sz_size_bit_ceil(127) == 128);
+    assert(sz_size_bit_ceil(128) == 128);
+
+    assert(sz_size_bit_ceil(uint64_t(1e6)) == (1ull << 20));
+    assert(sz_size_bit_ceil(uint64_t(2e6)) == (1ull << 21));
+    assert(sz_size_bit_ceil(uint64_t(4e6)) == (1ull << 22));
+    assert(sz_size_bit_ceil(uint64_t(8e6)) == (1ull << 23));
+
+    assert(sz_size_bit_ceil(uint64_t(1.6e7)) == (1ull << 24));
+    assert(sz_size_bit_ceil(uint64_t(3.2e7)) == (1ull << 25));
+    assert(sz_size_bit_ceil(uint64_t(6.4e7)) == (1ull << 26));
+
+    assert(sz_size_bit_ceil(uint64_t(1.28e8)) == (1ull << 27));
+    assert(sz_size_bit_ceil(uint64_t(2.56e8)) == (1ull << 28));
+    assert(sz_size_bit_ceil(uint64_t(5.12e8)) == (1ull << 29));
+
+    assert(sz_size_bit_ceil(uint64_t(1e9)) == (1ull << 30));
+    assert(sz_size_bit_ceil(uint64_t(2e9)) == (1ull << 31));
+    assert(sz_size_bit_ceil(uint64_t(4e9)) == (1ull << 32));
+    assert(sz_size_bit_ceil(uint64_t(8e9)) == (1ull << 33));
+
+    assert(sz_size_bit_ceil(uint64_t(1.6e10)) == (1ull << 34));
+
+    assert(sz_size_bit_ceil((1ull << 62)) == (1ull << 62));
+    assert(sz_size_bit_ceil((1ull << 62) + 1) == (1ull << 63));
+    assert(sz_size_bit_ceil((1ull << 63)) == (1ull << 63));
 }
 
-static int is_less(sz_sequence_t const *array_c, sz_size_t i, sz_size_t j) {
-    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
-    return array[i] < array[j];
+/**
+ *  @brief  Validates that `sz_move` and `sz_copy` work as expected,
+ *          comparing them to `std::memmove` and `std::memcpy`.
+ */
+static void test_memory_utilities() {
+    constexpr std::size_t size = 1024;
+    char body_stl[size];
+    char body_sz[size];
+
+    std::random_device rd;
+    std::mt19937 gen(rd());
+    std::uniform_int_distribution<> dis(0, 255);
+
+    std::generate(body_stl, body_stl + size, [&]() { return static_cast<char>(dis(gen)); });
+    std::copy(body_stl, body_stl + size, body_sz);
+
+    // Move the contents of both strings around, validating overall
+    // equivalency after every random iteration.
+    for (std::size_t i = 0; i < size; i++) {
+        std::size_t offset = gen() % size;
+        std::size_t length = gen() % (size - offset);
+        std::size_t destination = gen() % (size - length);
+
+        std::memmove(body_stl + destination, body_stl + offset, length);
+        sz_move(body_sz + destination, body_sz + offset, length);
+        assert(std::memcmp(body_stl, body_sz, size) == 0);
+    }
 }
 
-static int has_under_four_chars(sz_sequence_t const *array_c, sz_size_t i) {
-    strings_t const &array = *reinterpret_cast<strings_t const *>(array_c->handle);
-    return array[i].size() < 4;
+#define assert_scoped(init, operation, condition) \
+    {                                             \
+        init;                                     \
+        operation;                                \
+        assert(condition);                        \
+    }
+
+#define assert_throws(expression, exception_type) \
+    {                                             \
+        bool threw = false;                       \
+        try {                                     \
+            sz_unused(expression);                \
+        }                                         \
+        catch (exception_type const &) {          \
+            threw = true;                         \
+        }                                         \
+        assert(threw);                            \
+    }
+
+/**
+ *  @brief  Invokes different C++ member methods of immutable strings to cover all STL APIs.
+ *          This test guarantees API compatibility with STL `std::basic_string` template.
+ */
+template <typename string_type>
+static void test_api_readonly() {
+
+    using str = string_type;
+
+    // Constructors.
+    assert(str().empty());             // Test default constructor
+    assert(str().size() == 0);         // Test default constructor
+    assert(str("").empty());           // Test default constructor
+    assert(str("").size() == 0);       // Test default constructor
+    assert(str("hello").size() == 5);  // Test constructor with c-string
+    assert(str("hello", 4) == "hell"); // Construct from substring
+
+    // Element access.
+    assert(str("test")[0] == 't');
+    assert(str("test").at(1) == 'e');
+    assert(str("front").front() == 'f');
+    assert(str("back").back() == 'k');
+    assert(*str("data").data() == 'd');
+
+    // Iterators.
+    assert(*str("begin").begin() == 'b' && *str("cbegin").cbegin() == 'c');
+    assert(*str("rbegin").rbegin() == 'n' && *str("crbegin").crbegin() == 'n');
+    assert(str("size").size() == 4 && str("length").length() == 6);
+
+    // Slices... out-of-bounds exceptions are asymmetric!
+    // Moreover, `std::string` has no `remove_prefix` and `remove_suffix` methods.
+    // assert_scoped(str s = "hello", s.remove_prefix(1), s == "ello");
+    // assert_scoped(str s = "hello", s.remove_suffix(1), s == "hell");
+    assert(str("hello world").substr(0, 5) == "hello");
+    assert(str("hello world").substr(6, 5) == "world");
+    assert(str("hello world").substr(6) == "world");
+    assert(str("hello world").substr(6, 100) == "world"); // 106 is beyond the length of the string, but its OK
+    assert_throws(str("hello world").substr(100), std::out_of_range);   // 100 is beyond the length of the string
+    assert_throws(str("hello world").substr(20, 5), std::out_of_range); // 20 is beyond the length of the string
+    assert_throws(str("hello world").substr(-1, 5), std::out_of_range); // -1 casts to unsigned without any warnings...
+    assert(str("hello world").substr(0, -1) == "hello world");          // -1 casts to unsigned without any warnings...
+
+    // Character search in normal and reverse directions.
+    assert(str("hello").find('e') == 1);
+    assert(str("hello").find('e', 1) == 1);
+    assert(str("hello").find('e', 2) == str::npos);
+    assert(str("hello").rfind('l') == 3);
+    assert(str("hello").rfind('l', 2) == 2);
+    assert(str("hello").rfind('l', 1) == str::npos);
+
+    // Substring search in normal and reverse directions.
+    assert(str("hello").find("ell") == 1);
+    assert(str("hello").find("ell", 1) == 1);
+    assert(str("hello").find("ell", 2) == str::npos);
+    assert(str("hello").find("el", 1) == 1);
+    assert(str("hello").find("ell", 1, 2) == 1);
+    assert(str("hello").rfind("l") == 3);
+    assert(str("hello").rfind("l", 2) == 2);
+    assert(str("hello").rfind("l", 1) == str::npos);
+
+    // The second argument is the last possible value of the returned offset.
+    assert(str("hello").rfind("el", 1) == 1);
+    assert(str("hello").rfind("ell", 1) == 1);
+    assert(str("hello").rfind("ello", 1) == 1);
+    assert(str("hello").rfind("ell", 1, 2) == 1);
+
+    // More complex queries.
+    assert(str("abbabbaaaaaa").find("aa") == 6);
+    assert(str("abcdabcd").substr(2, 4).find("abc") == str::npos);
+
+    // ! `rfind` and `find_last_of` are not consistent in meaning of their arguments.
+    assert(str("hello").find_first_of("le") == 1);
+    assert(str("hello").find_first_of("le", 1) == 1);
+    assert(str("hello").find_last_of("le") == 3);
+    assert(str("hello").find_last_of("le", 2) == 2);
+    assert(str("hello").find_first_not_of("hel") == 4);
+    assert(str("hello").find_first_not_of("hel", 1) == 4);
+    assert(str("hello").find_last_not_of("hel") == 4);
+    assert(str("hello").find_last_not_of("hel", 4) == 4);
+
+    // Try longer strings to enforce SIMD.
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find('x') == 23);  // first byte
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find('X') == 49);  // first byte
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind('x') == 23); // last byte
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind('X') == 49); // last byte
+
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("xy") == 23);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("XY") == 49);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("xy") == 23); // last match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("XY") == 49); // last match
+
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("xyz") == 23);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("XYZ") == 49);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("xyz") == 23); // last match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("XYZ") == 49); // last match
+
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("xyzA") == 23);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("XYZ0") == 49);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("xyzA") == 23); // last match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("XYZ0") == 49); // last match
+
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find_first_of("xyz") == 23); // sets
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find_first_of("XYZ") == 49); // sets
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find_last_of("xyz") == 25);  // sets
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find_last_of("XYZ") == 51);  // sets
+
+    // Boundary consitions.
+    assert(str("hello").find_first_of("ox", 4) == 4);
+    assert(str("hello").find_first_of("ox", 5) == str::npos);
+    assert(str("hello").find_last_of("ox", 4) == 4);
+    assert(str("hello").find_last_of("ox", 5) == 4);
+    assert(str("hello").find_first_of("hx", 0) == 0);
+    assert(str("hello").find_last_of("hx", 0) == 0);
+
+    // Comparisons.
+    assert(str("a") != str("b"));
+    assert(str("a") < str("b"));
+    assert(str("a") <= str("b"));
+    assert(str("b") > str("a"));
+    assert(str("b") >= str("a"));
+    assert(str("a") < str("aa"));
+
+#if SZ_DETECT_CPP20 && __cpp_lib_three_way_comparison
+    // Spaceship operator instead of conventional comparions.
+    assert((str("a") <=> str("b")) == std::strong_ordering::less);
+    assert((str("b") <=> str("a")) == std::strong_ordering::greater);
+    assert((str("b") <=> str("b")) == std::strong_ordering::equal);
+    assert((str("a") <=> str("aa")) == std::strong_ordering::less);
+#endif
+
+    // Compare with another `str`.
+    assert(str("test").compare(str("test")) == 0);   // Equal strings
+    assert(str("apple").compare(str("banana")) < 0); // "apple" is less than "banana"
+    assert(str("banana").compare(str("apple")) > 0); // "banana" is greater than "apple"
+
+    // Compare with a C-string.
+    assert(str("test").compare("test") == 0); // Equal to C-string "test"
+    assert(str("alpha").compare("beta") < 0); // "alpha" is less than C-string "beta"
+    assert(str("beta").compare("alpha") > 0); // "beta" is greater than C-string "alpha"
+
+    // Compare substring with another `str`.
+    assert(str("hello world").compare(0, 5, str("hello")) == 0); // Substring "hello" is equal to "hello"
+    assert(str("hello world").compare(6, 5, str("earth")) > 0);  // Substring "world" is greater than "earth"
+    assert(str("hello world").compare(6, 5, str("worlds")) < 0); // Substring "world" is less than "worlds"
+    assert_throws(str("hello world").compare(20, 5, str("worlds")), std::out_of_range);
+
+    // Compare substring with another `str`'s substring.
+    assert(str("hello world").compare(0, 5, str("say hello"), 4, 5) == 0);      // Substring "hello" in both strings
+    assert(str("hello world").compare(6, 5, str("world peace"), 0, 5) == 0);    // Substring "world" in both strings
+    assert(str("hello world").compare(6, 5, str("a better world"), 9, 5) == 0); // Both substrings are "world"
+
+    // Out of bounds cases for both compared strings.
+    assert_throws(str("hello world").compare(20, 5, str("a better world"), 9, 5), std::out_of_range);
+    assert_throws(str("hello world").compare(6, 5, str("a better world"), 90, 5), std::out_of_range);
+
+    // Compare substring with a C-string.
+    assert(str("hello world").compare(0, 5, "hello") == 0); // Substring "hello" is equal to C-string "hello"
+    assert(str("hello world").compare(6, 5, "earth") > 0);  // Substring "world" is greater than C-string "earth"
+    assert(str("hello world").compare(6, 5, "worlds") < 0); // Substring "world" is greater than C-string "worlds"
+
+    // Compare substring with a C-string's prefix.
+    assert(str("hello world").compare(0, 5, "hello Ash", 5) == 0); // Substring "hello" in both strings
+    assert(str("hello world").compare(6, 5, "worlds", 5) == 0);    // Substring "world" in both strings
+    assert(str("hello world").compare(6, 5, "worlds", 6) < 0);     // Substring "world" is less than "worlds"
+
+#if SZ_DETECT_CPP20 && __cpp_lib_starts_ends_with
+    // Prefix and suffix checks against strings.
+    assert(str("https://cppreference.com").starts_with(str("http")) == true);
+    assert(str("https://cppreference.com").starts_with(str("ftp")) == false);
+    assert(str("https://cppreference.com").ends_with(str("com")) == true);
+    assert(str("https://cppreference.com").ends_with(str("org")) == false);
+
+    // Prefix and suffix checks against characters.
+    assert(str("C++20").starts_with('C') == true);
+    assert(str("C++20").starts_with('J') == false);
+    assert(str("C++20").ends_with('0') == true);
+    assert(str("C++20").ends_with('3') == false);
+
+    // Prefix and suffix checks against C-style strings.
+    assert(str("string_view").starts_with("string") == true);
+    assert(str("string_view").starts_with("String") == false);
+    assert(str("string_view").ends_with("view") == true);
+    assert(str("string_view").ends_with("View") == false);
+#endif
+
+#if SZ_DETECT_CPP_23 && __cpp_lib_string_contains
+    // Checking basic substring presence.
+    assert(str("hello").contains(str("ell")) == true);
+    assert(str("hello").contains(str("oll")) == false);
+    assert(str("hello").contains('l') == true);
+    assert(str("hello").contains('x') == false);
+    assert(str("hello").contains("lo") == true);
+    assert(str("hello").contains("lx") == false);
+#endif
+
+    // Exporting the contents of the string using the `str::copy` method.
+    assert_scoped(char buf[5 + 1] = {0}, str("hello").copy(buf, 5), std::strcmp(buf, "hello") == 0);
+    assert_scoped(char buf[4 + 1] = {0}, str("hello").copy(buf, 4, 1), std::strcmp(buf, "ello") == 0);
+    assert_throws(str("hello").copy((char *)"", 1, 100), std::out_of_range);
+
+    // Swaps.
+    for (str const first : {"", "hello", "hellohellohellohellohellohellohellohellohellohellohellohello"}) {
+        for (str const second : {"", "world", "worldworldworldworldworldworldworldworldworldworldworldworld"}) {
+            str first_copy = first;
+            str second_copy = second;
+            first_copy.swap(second_copy);
+            assert(first_copy == second && second_copy == first);
+            first_copy.swap(first_copy); // Swapping with itself.
+            assert(first_copy == second);
+        }
+    }
+
+    // Make sure the standard hash and function-objects instantiate just fine.
+    assert(std::hash<str> {}("hello") != 0);
+    assert_scoped(std::ostringstream os, os << str("hello"), os.str() == "hello");
+
+#if SZ_DETECT_CPP14
+    // Comparison function objects are a C++14 feature.
+    assert(std::equal_to<str> {}("hello", "world") == false);
+    assert(std::less<str> {}("hello", "world") == true);
+#endif
 }
 
-#pragma endregion
+/**
+ *  @brief  Invokes different C++ member methods of the memory-owning string class to make sure they all pass
+ *          compilation. This test guarantees API compatibility with STL `std::basic_string` template.
+ */
+template <typename string_type>
+static void test_api_mutable() {
 
-void populate_from_file( //
-    char const *path,
-    strings_t &strings,
-    std::size_t limit = std::numeric_limits<std::size_t>::max()) {
+    using str = string_type;
 
-    std::ifstream f(path, std::ios::in);
-    std::string s;
-    while (strings.size() < limit && std::getline(f, s, ' ')) strings.push_back(s);
+    // Constructors.
+    assert(str().empty());                             // Test default constructor
+    assert(str().size() == 0);                         // Test default constructor
+    assert(str("").empty());                           // Test default constructor
+    assert(str("").size() == 0);                       // Test default constructor
+    assert(str("hello").size() == 5);                  // Test constructor with c-string
+    assert(str("hello", 4) == "hell");                 // Construct from substring
+    assert(str(5, 'a') == "aaaaa");                    // Construct with count and character
+    assert(str({'h', 'e', 'l', 'l', 'o'}) == "hello"); // Construct from initializer list
+    assert(str(str("hello"), 2) == "llo");             // Construct from another string suffix
+    assert(str(str("hello"), 2, 2) == "ll");           // Construct from another string range
+
+    // Assignments.
+    assert_scoped(str s = "obsolete", s = "hello", s == "hello");
+    assert_scoped(str s = "obsolete", s.assign("hello"), s == "hello");
+    assert_scoped(str s = "obsolete", s.assign("hello", 4), s == "hell");
+    assert_scoped(str s = "obsolete", s.assign(5, 'a'), s == "aaaaa");
+    assert_scoped(str s = "obsolete", s.assign({'h', 'e', 'l', 'l', 'o'}), s == "hello");
+    assert_scoped(str s = "obsolete", s.assign(str("hello")), s == "hello");
+    assert_scoped(str s = "obsolete", s.assign(str("hello"), 2), s == "llo");
+    assert_scoped(str s = "obsolete", s.assign(str("hello"), 2, 2), s == "ll");
+    assert_scoped(str s = "obsolete", s.assign(str("hello"), 2, 2), s == "ll");
+    assert_scoped(str s = "obsolete", s.assign(s), s == "obsolete");                  // Self-assignment
+    assert_scoped(str s = "obsolete", s.assign(s.begin(), s.end()), s == "obsolete"); // Self-assignment
+    assert_scoped(str s = "obsolete", s.assign(s, 4), s == "lete");                   // Partial self-assignment
+    assert_scoped(str s = "obsolete", s.assign(s, 4, 3), s == "let");                 // Partial self-assignment
+
+    // Allocations, capacity and memory management.
+    assert_scoped(str s, s.reserve(10), s.capacity() >= 10);
+    assert_scoped(str s, s.resize(10), s.size() == 10);
+    assert_scoped(str s, s.resize(10, 'a'), s.size() == 10 && s == "aaaaaaaaaa");
+    assert(str().max_size() > 0);
+    assert(str().get_allocator() == std::allocator<char>());
+    assert(std::strcmp(str("c_str").c_str(), "c_str") == 0);
+
+    // Concatenation.
+    // Following are missing in strings, but are present in vectors.
+    // assert_scoped(str s = "!?", s.push_front('a'), s == "a!?");
+    // assert_scoped(str s = "!?", s.pop_front(), s == "?");
+    assert(str().append("test") == "test");
+    assert(str("test") + "ing" == "testing");
+    assert(str("test") + str("ing") == "testing");
+    assert(str("test") + str("ing") + str("123") == "testing123");
+    assert_scoped(str s = "!?", s.push_back('a'), s == "!?a");
+    assert_scoped(str s = "!?", s.pop_back(), s == "!");
+
+    // Incremental construction.
+    assert(str("__").insert(1, "test") == "_test_");
+    assert(str("__").insert(1, "test", 2) == "_te_");
+    assert(str("__").insert(1, 5, 'a') == "_aaaaa_");
+    assert(str("__").insert(1, str("test")) == "_test_");
+    assert(str("__").insert(1, str("test"), 2) == "_st_");
+    assert(str("__").insert(1, str("test"), 2, 1) == "_s_");
+
+    // Inserting at a given iterator position yields back an iterator.
+    assert_scoped(str s = "__", s.insert(s.begin() + 1, 5, 'a'), s == "_aaaaa_");
+    assert_scoped(str s = "__", s.insert(s.begin() + 1, {'a', 'b', 'c'}), s == "_abc_");
+    assert_scoped(str s = "__", (void)0, s.insert(s.begin() + 1, 5, 'a') == (s.begin() + 1));
+    assert_scoped(str s = "__", (void)0, s.insert(s.begin() + 1, {'a', 'b', 'c'}) == (s.begin() + 1));
+
+    // Handle exceptions.
+    // The `length_error` might be difficult to catch due to a large `max_size()`.
+    // assert_throws(large_string.insert(large_string.size() - 1, large_number_of_chars, 'a'), std::length_error);
+    assert_throws(str("hello").insert(6, "world"), std::out_of_range);         // `index > size()` case from STL
+    assert_throws(str("hello").insert(5, str("world"), 6), std::out_of_range); // `s_index > str.size()` case from STL
+
+    // Erasure.
+    assert(str("").erase(0, 3) == "");
+    assert(str("test").erase(1, 2) == "tt");
+    assert(str("test").erase(1) == "t");
+    assert_scoped(str s = "test", s.erase(s.begin() + 1), s == "tst");
+    assert_scoped(str s = "test", s.erase(s.begin() + 1, s.begin() + 2), s == "tst");
+    assert_scoped(str s = "test", s.erase(s.begin() + 1, s.begin() + 3), s == "tt");
+    assert_scoped(str s = "test", (void)0, s.erase(s.begin() + 1) == (s.begin() + 1));
+    assert_scoped(str s = "test", (void)0, s.erase(s.begin() + 1, s.begin() + 2) == (s.begin() + 1));
+    assert_scoped(str s = "test", (void)0, s.erase(s.begin() + 1, s.begin() + 3) == (s.begin() + 1));
+
+    // Substitutions.
+    assert(str("hello").replace(1, 2, "123") == "h123lo");
+    assert(str("hello").replace(1, 2, str("123"), 1) == "h23lo");
+    assert(str("hello").replace(1, 2, "123", 1) == "h1lo");
+    assert(str("hello").replace(1, 2, "123", 1, 1) == "h2lo");
+    assert(str("hello").replace(1, 2, str("123"), 1, 1) == "h2lo");
+    assert(str("hello").replace(1, 2, 3, 'a') == "haaalo");
+
+    // Substitutions with iterators.
+    assert_scoped(str s = "hello", s.replace(s.begin() + 1, s.begin() + 3, 3, 'a'), s == "haaalo");
+    assert_scoped(str s = "hello", s.replace(s.begin() + 1, s.begin() + 3, {'a', 'b'}), s == "hablo");
+
+    // Some nice "tweetable" examples :)
+    assert(str("Loose").replace(2, 2, str("vath"), 1) == "Loathe");
+    assert(str("Loose").replace(2, 2, "vath", 1) == "Love");
+
+    // Insertion is a special case of replacement.
+    // Appending and assigning are special cases of insertion.
+    // Still, we test them separately to make sure they are not broken.
+    assert(str("hello").append("123") == "hello123");
+    assert(str("hello").append(str("123")) == "hello123");
+    assert(str("hello").append(str("123"), 1) == "hello23");
+    assert(str("hello").append(str("123"), 1, 1) == "hello2");
+    assert(str("hello").append({'1', '2'}) == "hello12");
+    assert(str("hello").append(2, '!') == "hello!!");
+    assert_scoped(str s = "123", (void)0, str("hello").append(s.begin(), s.end()) == "hello123");
 }
 
-void populate_with_test(strings_t &strings) {
-    strings.push_back("bbbb");
-    strings.push_back("bbbbbb");
-    strings.push_back("aac");
-    strings.push_back("aa");
-    strings.push_back("bb");
-    strings.push_back("ab");
-    strings.push_back("a");
-    strings.push_back("");
-    strings.push_back("cccc");
-    strings.push_back("ccccccc");
+static void test_stl_conversion_api() {
+    // From a mutable STL string to StringZilla and vice-versa.
+    {
+        std::string stl {"hello"};
+        sz::string sz = stl;
+        sz::string_view szv = stl;
+        sz::string_span szs = stl;
+        stl = sz;
+        stl = szv;
+        stl = szs;
+    }
+    // From an immutable STL string to StringZilla.
+    {
+        std::string const stl {"hello"};
+        sz::string sz = stl;
+        sz::string_view szv = stl;
+        sz_unused(sz);
+        sz_unused(szv);
+    }
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+    // From STL `string_view` to StringZilla and vice-versa.
+    {
+        std::string_view stl {"hello"};
+        sz::string sz = stl;
+        sz::string_view szv = stl;
+        stl = sz;
+        stl = szv;
+    }
+#endif
 }
 
-constexpr size_t offset_in_word = 0;
+/**
+ *  @brief  Invokes different C++ member methods of immutable strings to cover
+ *          extensions beyond the STL API.
+ */
+template <typename string_type>
+static void test_api_readonly_extensions() {
+    using str = string_type;
 
-inline static idx_t hybrid_sort_cpp(strings_t const &strings, sz_u64_t *order) {
+    // Signed offset lokups and slices.
+    assert(str("hello").sat(0) == 'h');
+    assert(str("hello").sat(-1) == 'o');
+    assert(str("hello").sub(1) == "ello");
+    assert(str("hello").sub(-1) == "o");
+    assert(str("hello").sub(1, 2) == "e");
+    assert(str("hello").sub(1, 100) == "ello");
+    assert(str("hello").sub(100, 100) == "");
+    assert(str("hello").sub(-2, -1) == "l");
+    assert(str("hello").sub(-2, -2) == "");
+    assert(str("hello").sub(100, -100) == "");
 
-    // What if we take up-to 4 first characters and the index
-    for (size_t i = 0; i != strings.size(); ++i)
-        std::memcpy((char *)&order[i] + offset_in_word,
-                    strings[order[i]].c_str(),
-                    std::min(strings[order[i]].size(), 4ul));
+    // Passing initializer lists to `operator[]`.
+    // Put extra braces to correctly estimate the number of macro arguments :)
+    assert((str("hello")[{1, 2}] == "e"));
+    assert((str("hello")[{1, 100}] == "ello"));
+    assert((str("hello")[{100, 100}] == ""));
+    assert((str("hello")[{100, -100}] == ""));
+    assert((str("hello")[{-100, -100}] == ""));
 
-    std::sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) {
-        char *i_bytes = (char *)&i;
-        char *j_bytes = (char *)&j;
-        return *(uint32_t *)(i_bytes + offset_in_word) < *(uint32_t *)(j_bytes + offset_in_word);
-    });
+    // Computing edit-distances.
+    assert(sz::edit_distance(str("hello"), str("hello")) == 0);
+    assert(sz::edit_distance(str("hello"), str("hell")) == 1);
+    assert(sz::edit_distance(str(""), str("")) == 0);
+    assert(sz::edit_distance(str(""), str("abc")) == 3);
+    assert(sz::edit_distance(str("abc"), str("")) == 3);
+    assert(sz::edit_distance(str("abc"), str("ac")) == 1);                   // one deletion
+    assert(sz::edit_distance(str("abc"), str("a_bc")) == 1);                 // one insertion
+    assert(sz::edit_distance(str("abc"), str("adc")) == 1);                  // one substitution
+    assert(sz::edit_distance(str("ggbuzgjux{}l"), str("gbuzgjux{}l")) == 1); // one insertion (prepended)
+    assert(sz::edit_distance(str("abcdefgABCDEFG"), str("ABCDEFGabcdefg")) == 14);
 
-    for (size_t i = 0; i != strings.size(); ++i) std::memset((char *)&order[i] + offset_in_word, 0, 4ul);
+    // Computing alignment scores.
+    using matrix_t = std::int8_t[256][256];
+    std::vector<std::int8_t> costs_vector = unary_substitution_costs();
+    matrix_t &costs = *reinterpret_cast<matrix_t *>(costs_vector.data());
 
-    std::sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) { return strings[i] < strings[j]; });
+    assert(sz::alignment_score(str("listen"), str("silent"), costs, -1) == -4);
+    assert(sz::alignment_score(str("abcdefgABCDEFG"), str("ABCDEFGabcdefg"), costs, -1) == -14);
+    assert(sz::alignment_score(str("hello"), str("hello"), costs, -1) == 0);
+    assert(sz::alignment_score(str("hello"), str("hell"), costs, -1) == -1);
 
-    return strings.size();
-}
+    assert(sz::hashes_fingerprint<512>(str("aaaa"), 3).count() == 1);
 
-int hybrid_sort_c_compare_uint32_t(const void *a, const void *b) {
-    uint32_t int_a = *((uint32_t *)(((char *)a) + sizeof(sz_size_t) - 4));
-    uint32_t int_b = *((uint32_t *)(((char *)b) + sizeof(sz_size_t) - 4));
-    return (int_a < int_b) ? -1 : (int_a > int_b);
+    // Computing rolling fingerprints.
+    assert(sz::hashes_fingerprint<512>(str("hello"), 4).count() == 2);
+    assert(sz::hashes_fingerprint<512>(str("hello"), 3).count() == 3);
+
+    // No matter how many times one repeats a character, the hash should only contain at most one set bit.
+    assert(sz::hashes_fingerprint<512>(str("a"), 3).count() == 0);
+    assert(sz::hashes_fingerprint<512>(str("aa"), 3).count() == 0);
+    assert(sz::hashes_fingerprint<512>(str("aaa"), 3).count() == 1);
+    assert(sz::hashes_fingerprint<512>(str("aaaa"), 3).count() == 1);
+    assert(sz::hashes_fingerprint<512>(str("aaaaa"), 3).count() == 1);
+
+    // Computing fuzzy search results.
 }
 
-int hybrid_sort_c_compare_strings(void *arg, const void *a, const void *b) {
-    sz_sequence_t *sequence = (sz_sequence_t *)arg;
-    sz_size_t idx_a = *(sz_size_t *)a;
-    sz_size_t idx_b = *(sz_size_t *)b;
+void test_api_mutable_extensions() {
+    using str = sz::string;
+
+    // Same length replacements.
+    assert_scoped(str s = "hello", s.replace_all("xx", "xx"), s == "hello");
+    assert_scoped(str s = "hello", s.replace_all("l", "1"), s == "he11o");
+    assert_scoped(str s = "hello", s.replace_all("he", "al"), s == "alllo");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("x"), "!"), s == "hello");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("o"), "!"), s == "hell!");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("ho"), "!"), s == "!ell!");
+
+    // Shorter replacements.
+    assert_scoped(str s = "hello", s.replace_all("xx", "x"), s == "hello");
+    assert_scoped(str s = "hello", s.replace_all("l", ""), s == "heo");
+    assert_scoped(str s = "hello", s.replace_all("h", ""), s == "ello");
+    assert_scoped(str s = "hello", s.replace_all("o", ""), s == "hell");
+    assert_scoped(str s = "hello", s.replace_all("llo", "!"), s == "he!");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("x"), ""), s == "hello");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("lo"), ""), s == "he");
+
+    // Longer replacements.
+    assert_scoped(str s = "hello", s.replace_all("xx", "xxx"), s == "hello");
+    assert_scoped(str s = "hello", s.replace_all("l", "ll"), s == "hellllo");
+    assert_scoped(str s = "hello", s.replace_all("h", "hh"), s == "hhello");
+    assert_scoped(str s = "hello", s.replace_all("o", "oo"), s == "helloo");
+    assert_scoped(str s = "hello", s.replace_all("llo", "llo!"), s == "hello!");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("x"), "xx"), s == "hello");
+    assert_scoped(str s = "hello", s.replace_all(sz::char_set("lo"), "lo"), s == "helololo");
 
-    const char *str_a = sequence->get_start(sequence, idx_a);
-    const char *str_b = sequence->get_start(sequence, idx_b);
-    sz_size_t len_a = sequence->get_length(sequence, idx_a);
-    sz_size_t len_b = sequence->get_length(sequence, idx_b);
+    // Concatenation.
+    assert(str(str("a") | str("b")) == "ab");
+    assert(str(str("a") | str("b") | str("ab")) == "abab");
 
-    int res = strncmp(str_a, str_b, len_a < len_b ? len_a : len_b);
-    return res ? res : (int)(len_a - len_b);
+    assert(str(sz::concatenate("a"_sz, "b"_sz)) == "ab");
+    assert(str(sz::concatenate("a"_sz, "b"_sz, "c"_sz)) == "abc");
 }
 
-sz_size_t hybrid_sort_c(sz_sequence_t *sequence) {
-    // Copy up to 4 first characters into the 'order' array.
-    for (sz_size_t i = 0; i < sequence->count; ++i) {
-        const char *str = sequence->get_start(sequence, sequence->order[i]);
-        sz_size_t len = sequence->get_length(sequence, sequence->order[i]);
-        len = len > 4 ? 4 : len;
-        memcpy((char *)&sequence->order[i] + sizeof(sz_size_t) - 4, str, len);
+/**
+ *  @brief  Tests copy constructor and copy-assignment constructor of `sz::string`.
+ */
+static void test_constructors() {
+    std::string alphabet {sz::ascii_printables(), sizeof(sz::ascii_printables())};
+    std::vector<sz::string> strings;
+    for (std::size_t alphabet_slice = 0; alphabet_slice != alphabet.size(); ++alphabet_slice)
+        strings.push_back(alphabet.substr(0, alphabet_slice));
+    std::vector<sz::string> copies {strings};
+    assert(copies.size() == strings.size());
+    for (size_t i = 0; i < copies.size(); i++) {
+        assert(copies[i].size() == strings[i].size());
+        assert(copies[i] == strings[i]);
+        for (size_t j = 0; j < strings[i].size(); j++) { assert(copies[i][j] == strings[i][j]); }
+    }
+    std::vector<sz::string> assignments = strings;
+    for (size_t i = 0; i < assignments.size(); i++) {
+        assert(assignments[i].size() == strings[i].size());
+        assert(assignments[i] == strings[i]);
+        for (size_t j = 0; j < strings[i].size(); j++) { assert(assignments[i][j] == strings[i][j]); }
+    }
+    assert(std::equal(strings.begin(), strings.end(), copies.begin()));
+    assert(std::equal(strings.begin(), strings.end(), assignments.begin()));
+}
+
+struct accounting_allocator : public std::allocator<char> {
+    inline static bool &verbose_ref() {
+        static bool global_value = false;
+        return global_value;
+    }
+    inline static std::size_t &counter_ref() {
+        static std::size_t global_value = 0ul;
+        return global_value;
+    }
+
+    template <typename... args_types>
+    static void print_if_verbose(char const *fmt, args_types... args) {
+        if (!verbose_ref()) return;
+        std::printf(fmt, args...);
     }
 
-    // Sort based on the first 4 bytes.
-    qsort(sequence->order, sequence->count, sizeof(sz_size_t), hybrid_sort_c_compare_uint32_t);
+    char *allocate(std::size_t n) {
+        counter_ref() += n;
+        print_if_verbose("alloc %zd -> %zd\n", n, counter_ref());
+        return std::allocator<char>::allocate(n);
+    }
 
-    // Clear the 4 bytes used for the initial sort.
-    for (sz_size_t i = 0; i < sequence->count; ++i) {
-        memset((char *)&sequence->order[i] + sizeof(sz_size_t) - 4, 0, 4);
+    void deallocate(char *val, std::size_t n) {
+        assert(n <= counter_ref());
+        counter_ref() -= n;
+        print_if_verbose("dealloc: %zd -> %zd\n", n, counter_ref());
+        std::allocator<char>::deallocate(val, n);
     }
 
-    // Sort the full strings.
-    qsort_r(sequence->order, sequence->count, sizeof(sz_size_t), sequence, hybrid_sort_c_compare_strings);
+    template <typename callback_type>
+    static std::size_t account_block(callback_type callback) {
+        auto before = accounting_allocator::counter_ref();
+        print_if_verbose("starting block: %zd\n", before);
+        callback();
+        auto after = accounting_allocator::counter_ref();
+        print_if_verbose("ending block: %zd\n", after);
+        return after - before;
+    }
+};
 
-    return sequence->count;
+template <typename callback_type>
+void assert_balanced_memory(callback_type callback) {
+    auto bytes = accounting_allocator::account_block(callback);
+    assert(bytes == 0);
 }
 
-inline static idx_t hybrid_stable_sort_cpp(strings_t const &strings, sz_u64_t *order) {
+static void test_memory_stability_for_length(std::size_t len = 1ull << 10) {
+    std::size_t iterations = 4;
 
-    // What if we take up-to 4 first characters and the index
-    for (size_t i = 0; i != strings.size(); ++i)
-        std::memcpy((char *)&order[i] + offset_in_word,
-                    strings[order[i]].c_str(),
-                    std::min(strings[order[i]].size(), 4ul));
+    assert(accounting_allocator::counter_ref() == 0);
+    using string = sz::basic_string<char, accounting_allocator>;
+    string base;
 
-    std::stable_sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) {
-        char *i_bytes = (char *)&i;
-        char *j_bytes = (char *)&j;
-        return *(uint32_t *)(i_bytes + offset_in_word) < *(uint32_t *)(j_bytes + offset_in_word);
+    for (std::size_t i = 0; i < len; i++) base.push_back('c');
+    assert(base.length() == len);
+
+    // Do copies leak?
+    assert_balanced_memory([&]() {
+        for (std::size_t i = 0; i < iterations; i++) {
+            string copy(base);
+            assert(copy.length() == len);
+            assert(copy == base);
+        }
     });
 
-    for (size_t i = 0; i != strings.size(); ++i) std::memset((char *)&order[i] + offset_in_word, 0, 4ul);
+    // How about assignments?
+    assert_balanced_memory([&]() {
+        for (std::size_t i = 0; i < iterations; i++) {
+            string copy;
+            copy = base;
+            assert(copy.length() == len);
+            assert(copy == base);
+        }
+    });
 
-    std::stable_sort(order, order + strings.size(), [&](sz_u64_t i, sz_u64_t j) { return strings[i] < strings[j]; });
+    // How about the move ctor?
+    assert_balanced_memory([&]() {
+        for (std::size_t i = 0; i < iterations; i++) {
+            string unique_item(base);
+            assert(unique_item.length() == len);
+            assert(unique_item == base);
+            string copy(std::move(unique_item));
+            assert(copy.length() == len);
+            assert(copy == base);
+        }
+    });
 
-    return strings.size();
+    // And the move assignment operator with an empty target payload?
+    assert_balanced_memory([&]() {
+        for (std::size_t i = 0; i < iterations; i++) {
+            string unique_item(base);
+            string copy;
+            copy = std::move(unique_item);
+            assert(copy.length() == len);
+            assert(copy == base);
+        }
+    });
+
+    // And move assignment where the target had a payload?
+    assert_balanced_memory([&]() {
+        for (std::size_t i = 0; i < iterations; i++) {
+            string unique_item(base);
+            string copy;
+            for (std::size_t j = 0; j < 317; j++) copy.push_back('q');
+            copy = std::move(unique_item);
+            assert(copy.length() == len);
+            assert(copy == base);
+        }
+    });
+
+    // Now let's clear the base and check that we're back to zero
+    base = string();
+    assert(accounting_allocator::counter_ref() == 0);
 }
 
-void expect_partitioned_by_length(strings_t const &strings, permute_t const &permute) {
-    if (!std::is_partitioned(permute.begin(), permute.end(), [&](size_t i) { return strings[i].size() < 4; }))
-        throw std::runtime_error("Partitioning failed!");
+/**
+ *  @brief  Tests the correctness of the string class update methods, such as `append` and `erase`.
+ */
+static void test_updates() {
+    // Compare STL and StringZilla strings append functionality.
+    char const alphabet_chars[] = "abcdefghijklmnopqrstuvwxyz";
+    std::string stl_string;
+    sz::string sz_string;
+    for (std::size_t length = 1; length != 200; ++length) {
+        char c = alphabet_chars[std::rand() % 26];
+        stl_string.push_back(c);
+        sz_string.push_back(c);
+        assert(sz::string_view(stl_string) == sz::string_view(sz_string));
+    }
+
+    // Compare STL and StringZilla strings erase functionality.
+    while (stl_string.length()) {
+        std::size_t offset_to_erase = std::rand() % stl_string.length();
+        std::size_t chars_to_erase = std::rand() % (stl_string.length() - offset_to_erase) + 1;
+        stl_string.erase(offset_to_erase, chars_to_erase);
+        sz_string.erase(offset_to_erase, chars_to_erase);
+        assert(sz::string_view(stl_string) == sz::string_view(sz_string));
+    }
 }
 
-void expect_sorted(strings_t const &strings, permute_t const &permute) {
-    if (!std::is_sorted(permute.begin(), permute.end(), [&](std::size_t i, std::size_t j) {
-            return strings[i] < strings[j];
-        }))
-        throw std::runtime_error("Sorting failed!");
+/**
+ *  @brief  Tests the correctness of the string class comparison methods, such as `compare` and `operator==`.
+ */
+static void test_comparisons() {
+    // Comparing relative order of the strings
+    assert("a"_sz.compare("a") == 0);
+    assert("a"_sz.compare("ab") == -1);
+    assert("ab"_sz.compare("a") == 1);
+    assert("a"_sz.compare("a\0"_sz) == -1);
+    assert("a\0"_sz.compare("a") == 1);
+    assert("a\0"_sz.compare("a\0"_sz) == 0);
+    assert("a"_sz == "a"_sz);
+    assert("a"_sz != "a\0"_sz);
+    assert("a\0"_sz == "a\0"_sz);
 }
 
-void expect_same(permute_t const &permute_base, permute_t const &permute_new) {
-    if (!std::equal(permute_base.begin(), permute_base.end(), permute_new.begin()))
-        throw std::runtime_error("Permutations differ!");
+/**
+ *  @brief  Tests the correctness of the string class search methods, such as `find` and `find_first_of`.
+ *          This covers haystacks and needles of different lengths, as well as character-sets.
+ */
+static void test_search() {
+
+    // Searching for a set of characters
+    assert(sz::string_view("a").find_first_of("az") == 0);
+    assert(sz::string_view("a").find_last_of("az") == 0);
+    assert(sz::string_view("a").find_first_of("xz") == sz::string_view::npos);
+    assert(sz::string_view("a").find_last_of("xz") == sz::string_view::npos);
+
+    assert(sz::string_view("a").find_first_not_of("xz") == 0);
+    assert(sz::string_view("a").find_last_not_of("xz") == 0);
+    assert(sz::string_view("a").find_first_not_of("az") == sz::string_view::npos);
+    assert(sz::string_view("a").find_last_not_of("az") == sz::string_view::npos);
+
+    assert(sz::string_view("aXbYaXbY").find_first_of("XY") == 1);
+    assert(sz::string_view("axbYaxbY").find_first_of("Y") == 3);
+    assert(sz::string_view("YbXaYbXa").find_last_of("XY") == 6);
+    assert(sz::string_view("YbxaYbxa").find_last_of("Y") == 4);
+    assert(sz::string_view(sz::base64(), sizeof(sz::base64())).find_first_of("_") == sz::string_view::npos);
+    assert(sz::string_view(sz::base64(), sizeof(sz::base64())).find_first_of("+") == 62);
+    assert(sz::string_view(sz::ascii_printables(), sizeof(sz::ascii_printables())).find_first_of("~") !=
+           sz::string_view::npos);
+
+    assert("aabaa"_sz.remove_prefix("a") == "abaa");
+    assert("aabaa"_sz.remove_suffix("a") == "aaba");
+    assert("aabaa"_sz.lstrip(sz::char_set {"a"}) == "baa");
+    assert("aabaa"_sz.rstrip(sz::char_set {"a"}) == "aab");
+    assert("aabaa"_sz.strip(sz::char_set {"a"}) == "b");
+
+    // Check more advanced composite operations
+    assert("abbccc"_sz.partition("bb").before.size() == 1);
+    assert("abbccc"_sz.partition("bb").match.size() == 2);
+    assert("abbccc"_sz.partition("bb").after.size() == 3);
+    assert("abbccc"_sz.partition("bb").before == "a");
+    assert("abbccc"_sz.partition("bb").match == "bb");
+    assert("abbccc"_sz.partition("bb").after == "ccc");
+
+    // Check ranges of search matches
+    assert("hello"_sz.find_all("l").size() == 2);
+    assert("hello"_sz.rfind_all("l").size() == 2);
+
+    assert(""_sz.find_all(".", sz::include_overlaps_type {}).size() == 0);
+    assert(""_sz.find_all(".", sz::exclude_overlaps_type {}).size() == 0);
+    assert("."_sz.find_all(".", sz::include_overlaps_type {}).size() == 1);
+    assert("."_sz.find_all(".", sz::exclude_overlaps_type {}).size() == 1);
+    assert(".."_sz.find_all(".", sz::include_overlaps_type {}).size() == 2);
+    assert(".."_sz.find_all(".", sz::exclude_overlaps_type {}).size() == 2);
+    assert(""_sz.rfind_all(".", sz::include_overlaps_type {}).size() == 0);
+    assert(""_sz.rfind_all(".", sz::exclude_overlaps_type {}).size() == 0);
+    assert("."_sz.rfind_all(".", sz::include_overlaps_type {}).size() == 1);
+    assert("."_sz.rfind_all(".", sz::exclude_overlaps_type {}).size() == 1);
+    assert(".."_sz.rfind_all(".", sz::include_overlaps_type {}).size() == 2);
+    assert(".."_sz.rfind_all(".", sz::exclude_overlaps_type {}).size() == 2);
+
+    assert("a.b.c.d"_sz.find_all(".").size() == 3);
+    assert("a.,b.,c.,d"_sz.find_all(".,").size() == 3);
+    assert("a.,b.,c.,d"_sz.rfind_all(".,").size() == 3);
+    assert("a.b,c.d"_sz.find_all(sz::char_set(".,")).size() == 3);
+    assert("a...b...c"_sz.rfind_all("..").size() == 4);
+    assert("a...b...c"_sz.rfind_all("..", sz::include_overlaps_type {}).size() == 4);
+    assert("a...b...c"_sz.rfind_all("..", sz::exclude_overlaps_type {}).size() == 2);
+
+    auto finds = "a.b.c"_sz.find_all(sz::char_set("abcd")).template to<std::vector<std::string>>();
+    assert(finds.size() == 3);
+    assert(finds[0] == "a");
+
+    auto rfinds = "a.b.c"_sz.rfind_all(sz::char_set("abcd")).template to<std::vector<std::string>>();
+    assert(rfinds.size() == 3);
+    assert(rfinds[0] == "c");
+
+    auto splits = ".a..c."_sz.split(sz::char_set(".")).template to<std::vector<std::string>>();
+    assert(splits.size() == 5);
+    assert(splits[0] == "");
+    assert(splits[1] == "a");
+    assert(splits[4] == "");
+
+    assert(""_sz.split(".").size() == 1);
+    assert(""_sz.rsplit(".").size() == 1);
+
+    assert("hello"_sz.split("l").size() == 3);
+    assert("hello"_sz.rsplit("l").size() == 3);
+    assert(*advanced("hello"_sz.split("l").begin(), 0) == "he");
+    assert(*advanced("hello"_sz.rsplit("l").begin(), 0) == "o");
+    assert(*advanced("hello"_sz.split("l").begin(), 1) == "");
+    assert(*advanced("hello"_sz.rsplit("l").begin(), 1) == "");
+    assert(*advanced("hello"_sz.split("l").begin(), 2) == "o");
+    assert(*advanced("hello"_sz.rsplit("l").begin(), 2) == "he");
+
+    assert("a.b.c.d"_sz.split(".").size() == 4);
+    assert("a.b.c.d"_sz.rsplit(".").size() == 4);
+    assert(*("a.b.c.d"_sz.split(".").begin()) == "a");
+    assert(*("a.b.c.d"_sz.rsplit(".").begin()) == "d");
+    assert(*advanced("a.b.c.d"_sz.split(".").begin(), 1) == "b");
+    assert(*advanced("a.b.c.d"_sz.rsplit(".").begin(), 1) == "c");
+    assert(*advanced("a.b.c.d"_sz.split(".").begin(), 3) == "d");
+    assert(*advanced("a.b.c.d"_sz.rsplit(".").begin(), 3) == "a");
+    assert("a.b.,c,d"_sz.split(".,").size() == 2);
+    assert("a.b,c.d"_sz.split(sz::char_set(".,")).size() == 4);
+
+    auto rsplits = ".a..c."_sz.rsplit(sz::char_set(".")).template to<std::vector<std::string>>();
+    assert(rsplits.size() == 5);
+    assert(rsplits[0] == "");
+    assert(rsplits[1] == "c");
+    assert(rsplits[4] == "");
 }
 
-template <typename algo_at>
-void bench_permute(char const *name, strings_t &strings, permute_t &permute, algo_at &&algo) {
-    namespace stdc = std::chrono;
-    using stdcc = stdc::high_resolution_clock;
-    constexpr std::size_t iterations = 3;
-    stdcc::time_point t1 = stdcc::now();
-
-    // Run multiple iterations
-    for (std::size_t i = 0; i != iterations; ++i) {
-        std::iota(permute.begin(), permute.end(), 0);
-        algo(strings, permute);
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+
+/**
+ *  Evaluates the correctness of a "matcher", searching for all the occurrences of the `needle_stl`
+ *  in a haystack formed of `haystack_pattern` repeated from one to `max_repeats` times.
+ *
+ *  @param misalignment The number of bytes to misalign the haystack within the cacheline.
+ */
+template <typename stl_matcher_, typename sz_matcher_>
+void test_search_with_misaligned_repetitions(std::string_view haystack_pattern, std::string_view needle_stl,
+                                             std::size_t misalignment) {
+    constexpr std::size_t max_repeats = 128;
+
+    // Allocate a buffer to store the haystack with enough padding to misalign it.
+    std::size_t haystack_buffer_length = max_repeats * haystack_pattern.size() + 2 * SZ_CACHE_LINE_WIDTH;
+    std::vector<char> haystack_buffer(haystack_buffer_length, 'x');
+    char *haystack = haystack_buffer.data();
+
+    // Skip the misaligned part.
+    while (reinterpret_cast<std::uintptr_t>(haystack) % SZ_CACHE_LINE_WIDTH != misalignment) ++haystack;
+
+    /// Helper container to store the offsets of the matches. Useful during debugging :)
+    std::vector<std::size_t> offsets_stl, offsets_sz;
+
+    for (std::size_t repeats = 0; repeats != max_repeats; ++repeats) {
+        std::size_t haystack_length = (repeats + 1) * haystack_pattern.size();
+        std::size_t poisoned_prefix_length = haystack - haystack_buffer.data();
+        std::size_t poisoned_suffix_length = haystack_buffer_length - haystack_length - poisoned_prefix_length;
+
+        // Let's manually poison the prefix and the suffix.
+        ASAN_POISON_MEMORY_REGION(haystack_buffer.data(), poisoned_prefix_length);
+        ASAN_POISON_MEMORY_REGION(haystack + haystack_length, poisoned_suffix_length);
+
+        // Append the new repetition to our buffer.
+        std::memcpy(haystack + repeats * haystack_pattern.size(), haystack_pattern.data(), haystack_pattern.size());
+
+        // Convert to string views
+        auto haystack_stl = std::string_view(haystack, haystack_length);
+        auto haystack_sz = sz::string_view(haystack, haystack_length);
+        auto needle_sz = sz::string_view(needle_stl.data(), needle_stl.size());
+
+        // Wrap into ranges
+        auto matches_stl = stl_matcher_(haystack_stl, {needle_stl});
+        auto matches_sz = sz_matcher_(haystack_sz, {needle_sz});
+        auto begin_stl = matches_stl.begin();
+        auto begin_sz = matches_sz.begin();
+        auto end_stl = matches_stl.end();
+        auto end_sz = matches_sz.end();
+        auto count_stl = std::distance(begin_stl, end_stl);
+        auto count_sz = std::distance(begin_sz, end_sz);
+
+        // To simplify debugging, let's first export all the match offsets, and only then compare them
+        std::transform(begin_stl, end_stl, std::back_inserter(offsets_stl),
+                       [&](auto const &match) { return match.data() - haystack_stl.data(); });
+        std::transform(begin_sz, end_sz, std::back_inserter(offsets_sz),
+                       [&](auto const &match) { return match.data() - haystack_sz.data(); });
+        auto print_all_matches = [&]() {
+            std::printf("Breakdown of found matches:\n");
+            std::printf("- STL (%zu): ", offsets_stl.size());
+            for (auto offset : offsets_stl) std::printf("%zu ", offset);
+            std::printf("\n");
+            std::printf("- StringZilla (%zu): ", offsets_sz.size());
+            for (auto offset : offsets_sz) std::printf("%zu ", offset);
+            std::printf("\n");
+        };
+
+        // Compare results
+        for (std::size_t match_idx = 0; begin_stl != end_stl && begin_sz != end_sz;
+             ++begin_stl, ++begin_sz, ++match_idx) {
+            auto match_stl = *begin_stl;
+            auto match_sz = *begin_sz;
+            if (match_stl.data() != match_sz.data()) {
+                std::printf("Mismatch at index #%zu: %zu != %zu\n", match_idx, match_stl.data() - haystack_stl.data(),
+                            match_sz.data() - haystack_sz.data());
+                print_all_matches();
+                assert(false);
+            }
+        }
+
+        // If one range is not finished, assert failure
+        if (count_stl != count_sz) {
+            print_all_matches();
+            assert(false);
+        }
+        assert(begin_stl == end_stl && begin_sz == end_sz);
+
+        offsets_stl.clear();
+        offsets_sz.clear();
+
+        // Don't forget to manually unpoison the prefix and the suffix.
+        ASAN_UNPOISON_MEMORY_REGION(haystack_buffer.data(), poisoned_prefix_length);
+        ASAN_UNPOISON_MEMORY_REGION(haystack + haystack_length, poisoned_suffix_length);
     }
+}
+
+/**
+ *  Evaluates the correctness of a "matcher", searching for all the occurrences of the `needle_stl`,
+ *  as a substring, as a set of allowed characters, or as a set of disallowed characters, in a haystack.
+ */
+void test_search_with_misaligned_repetitions(std::string_view haystack_pattern, std::string_view needle_stl,
+                                             std::size_t misalignment) {
+
+    test_search_with_misaligned_repetitions<                                     //
+        sz::range_matches<std::string_view, sz::matcher_find<std::string_view>>, //
+        sz::range_matches<sz::string_view, sz::matcher_find<sz::string_view>>>(  //
+        haystack_pattern, needle_stl, misalignment);
 
-    // Measure elapsed time
-    stdcc::time_point t2 = stdcc::now();
-    double dif = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count();
-    double milisecs = dif / (iterations * 1e6);
-    std::printf("Elapsed time is %.2lf miliseconds/iteration for %s.\n", milisecs, name);
+    test_search_with_misaligned_repetitions<                                       //
+        sz::range_rmatches<std::string_view, sz::matcher_rfind<std::string_view>>, //
+        sz::range_rmatches<sz::string_view, sz::matcher_rfind<sz::string_view>>>(  //
+        haystack_pattern, needle_stl, misalignment);
+
+    test_search_with_misaligned_repetitions<                                              //
+        sz::range_matches<std::string_view, sz::matcher_find_first_of<std::string_view>>, //
+        sz::range_matches<sz::string_view, sz::matcher_find_first_of<sz::string_view>>>(  //
+        haystack_pattern, needle_stl, misalignment);
+
+    test_search_with_misaligned_repetitions<                                              //
+        sz::range_rmatches<std::string_view, sz::matcher_find_last_of<std::string_view>>, //
+        sz::range_rmatches<sz::string_view, sz::matcher_find_last_of<sz::string_view>>>(  //
+        haystack_pattern, needle_stl, misalignment);
+
+    test_search_with_misaligned_repetitions<                                                  //
+        sz::range_matches<std::string_view, sz::matcher_find_first_not_of<std::string_view>>, //
+        sz::range_matches<sz::string_view, sz::matcher_find_first_not_of<sz::string_view>>>(  //
+        haystack_pattern, needle_stl, misalignment);
+
+    test_search_with_misaligned_repetitions<                                                  //
+        sz::range_rmatches<std::string_view, sz::matcher_find_last_not_of<std::string_view>>, //
+        sz::range_rmatches<sz::string_view, sz::matcher_find_last_not_of<sz::string_view>>>(  //
+        haystack_pattern, needle_stl, misalignment);
 }
 
-template <typename algo_at>
-void bench_search(char const *name, std::string_view full_text, algo_at &&algo) {
-    namespace stdc = std::chrono;
-    using stdcc = stdc::high_resolution_clock;
-    constexpr std::size_t iterations = 200;
-    stdcc::time_point t1 = stdcc::now();
-
-    // Run multiple iterations
-    std::size_t bytes_passed = 0;
-    for (std::size_t i = 0; i != iterations; ++i) bytes_passed += algo();
-
-    // Measure elapsed time
-    stdcc::time_point t2 = stdcc::now();
-    double dif = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count();
-    double milisecs = dif / (iterations * 1e6);
-    double gbs = bytes_passed / dif;
-    std::printf("Elapsed time is %.2lf miliseconds/iteration @ %.2f GB/s for %s.\n", milisecs, gbs, name);
+void test_search_with_misaligned_repetitions(std::string_view haystack_pattern, std::string_view needle_stl) {
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 0);
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 1);
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 2);
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 3);
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 63);
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 24);
+    test_search_with_misaligned_repetitions(haystack_pattern, needle_stl, 33);
 }
 
-int main(int, char const **) {
-    std::printf("Hey, Ash!\n");
+/**
+ *  @brief  Extensively tests the correctness of the string class search methods, such as `find` and `find_first_of`.
+ *          Covers different alignment cases within a cache line, repetitive patterns, and overlapping matches.
+ */
+static void test_search_with_misaligned_repetitions() {
+    // When haystack is only formed of needles:
+    test_search_with_misaligned_repetitions("a", "a");
+    test_search_with_misaligned_repetitions("ab", "ab");
+    test_search_with_misaligned_repetitions("abc", "abc");
+    test_search_with_misaligned_repetitions("abcd", "abcd");
+    test_search_with_misaligned_repetitions({sz::base64(), sizeof(sz::base64())}, {sz::base64(), sizeof(sz::base64())});
+    test_search_with_misaligned_repetitions({sz::ascii_lowercase(), sizeof(sz::ascii_lowercase())},
+                                            {sz::ascii_lowercase(), sizeof(sz::ascii_lowercase())});
+    test_search_with_misaligned_repetitions({sz::ascii_printables(), sizeof(sz::ascii_printables())},
+                                            {sz::ascii_printables(), sizeof(sz::ascii_printables())});
 
-    strings_t strings;
-    populate_from_file("leipzig1M.txt", strings, 1000000);
-    std::size_t mean_bytes = 0;
-    for (std::string const &str : strings) mean_bytes += str.size();
-    mean_bytes /= strings.size();
-    std::printf("Parsed the file with %zu words of %zu mean length!\n", strings.size(), mean_bytes);
+    // When we are dealing with NULL characters inside the string
+    test_search_with_misaligned_repetitions("\0", "\0");
+    test_search_with_misaligned_repetitions("a\0", "a\0");
+    test_search_with_misaligned_repetitions("ab\0", "ab");
+    test_search_with_misaligned_repetitions("ab\0", "ab\0");
+    test_search_with_misaligned_repetitions("abc\0", "abc");
+    test_search_with_misaligned_repetitions("abc\0", "abc\0");
+    test_search_with_misaligned_repetitions("abcd\0", "abcd");
 
-    std::string full_text;
-    full_text.reserve(mean_bytes + strings.size() * 2);
-    for (std::string const &str : strings) full_text.append(str), full_text.push_back(' ');
+    // When haystack is formed of equidistant needles:
+    test_search_with_misaligned_repetitions("ab", "a");
+    test_search_with_misaligned_repetitions("abc", "a");
+    test_search_with_misaligned_repetitions("abcd", "a");
 
-    auto make_random_needle = [](std::string_view full_text) {
-        std::size_t length = std::rand() % 6 + 2;
-        std::size_t offset = std::rand() % (full_text.size() - length);
-        return full_text.substr(offset, length);
-    };
+    // When matches occur in between pattern words:
+    test_search_with_misaligned_repetitions("ab", "ba");
+    test_search_with_misaligned_repetitions("abc", "ca");
+    test_search_with_misaligned_repetitions("abcd", "da");
+
+    // Examples targeted exactly against the Raita heuristic,
+    // which matches the first, the last, and the middle characters with SIMD.
+    test_search_with_misaligned_repetitions("aaabbccc", "aaabbccc");
+    test_search_with_misaligned_repetitions("axabbcxc", "aaabbccc");
+    test_search_with_misaligned_repetitions("axabbcxcaaabbccc", "aaabbccc");
+}
 
-    // Search substring
-    for (std::size_t needle_len = 1; needle_len <= 0; ++needle_len) {
-        std::string needle(needle_len, '\4');
-        std::printf("---- Needle length: %zu\n", needle_len);
-        bench_search("std::search", full_text, [&]() mutable {
-            return std::search(full_text.begin(), full_text.end(), needle.begin(), needle.end()) - full_text.begin();
-        });
-        bench_search("sz_find_substring_swar", full_text, [&]() mutable {
-            sz_string_start_t ptr =
-                sz_find_substring_swar(full_text.data(), full_text.size(), needle.data(), needle.size());
-            return ptr ? ptr - full_text.data() : full_text.size();
-        });
-#if defined(__ARM_NEON)
-        bench_search("sz_find_substring_neon", full_text, [&]() mutable {
-            sz_string_start_t ptr =
-                sz_find_substring_neon(full_text.data(), full_text.size(), needle.data(), needle.size());
-            return ptr ? ptr - full_text.data() : full_text.size();
-        });
-#endif
-#if defined(__AVX2__)
-        bench_search("sz_find_substring_avx2", full_text, [&]() mutable {
-            sz_string_start_t ptr =
-                sz_find_substring_avx2(full_text.data(), full_text.size(), needle.data(), needle.size());
-            return ptr ? ptr - full_text.data() : full_text.size();
-        });
 #endif
+
+/**
+ *  @brief  Tests the correctness of the string class Levenshtein distance computation,
+ *          as well as the similarity scoring functions for bioinformatics-like workloads.
+ */
+static void test_levenshtein_distances() {
+    struct {
+        char const *left;
+        char const *right;
+        std::size_t distance;
+    } explicit_cases[] = {
+        {"listen", "silent", 4},
+        {"", "", 0},
+        {"", "abc", 3},
+        {"abc", "", 3},
+        {"abc", "ac", 1},                   // one deletion
+        {"abc", "a_bc", 1},                 // one insertion
+        {"abc", "adc", 1},                  // one substitution
+        {"abc", "abc", 0},                  // same string
+        {"ggbuzgjux{}l", "gbuzgjux{}l", 1}, // one insertion (prepended)
+    };
+
+    using matrix_t = std::int8_t[256][256];
+    std::vector<std::int8_t> costs_vector = unary_substitution_costs();
+    matrix_t &costs = *reinterpret_cast<matrix_t *>(costs_vector.data());
+
+    auto print_failure = [&](char const *name, sz::string const &l, sz::string const &r, std::size_t expected,
+                             std::size_t received) {
+        char const *ellipsis = l.length() > 22 || r.length() > 22 ? "..." : "";
+        std::printf("%s error: distance(\"%.22s%s\", \"%.22s%s\"); got %zd, expected %zd\n", //
+                    name, l.c_str(), ellipsis, r.c_str(), ellipsis, received, expected);
+    };
+
+    auto test_distance = [&](sz::string const &l, sz::string const &r, std::size_t expected) {
+        auto received = sz::edit_distance(l, r);
+        auto received_score = sz::alignment_score(l, r, costs, -1);
+        if (received != expected) print_failure("Levenshtein", l, r, expected, received);
+        if ((std::size_t)(-received_score) != expected) print_failure("Scoring", l, r, expected, received_score);
+        // The distance relation commutes
+        received = sz::edit_distance(r, l);
+        received_score = sz::alignment_score(r, l, costs, -1);
+        if (received != expected) print_failure("Levenshtein", r, l, expected, received);
+        if ((std::size_t)(-received_score) != expected) print_failure("Scoring", r, l, expected, received_score);
+    };
+
+    for (auto explicit_case : explicit_cases)
+        test_distance(sz::string(explicit_case.left), sz::string(explicit_case.right), explicit_case.distance);
+
+    // Gradually increasing the length of the strings.
+    for (std::size_t length = 0; length != 1000; ++length) {
+        sz::string left, right;
+        for (std::size_t i = 0; i != length; ++i) left.push_back('a'), right.push_back('b');
+        test_distance(left, right, length);
     }
 
-    permute_t permute_base, permute_new;
-    permute_base.resize(strings.size());
-    permute_new.resize(strings.size());
-
-    // Partitioning
-    if (false) {
-        std::printf("---- Partitioning:\n");
-        bench_permute("std::partition", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
-            std::partition(permute.begin(), permute.end(), [&](size_t i) { return strings[i].size() < 4; });
-        });
-        expect_partitioned_by_length(strings, permute_base);
-
-        bench_permute("std::stable_partition", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
-            std::stable_partition(permute.begin(), permute.end(), [&](size_t i) { return strings[i].size() < 4; });
-        });
-        expect_partitioned_by_length(strings, permute_base);
-
-        bench_permute("sz_partition", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
-            sz_sequence_t array;
-            array.order = permute.data();
-            array.count = strings.size();
-            array.handle = &strings;
-            sz_partition(&array, &has_under_four_chars);
-        });
-        expect_partitioned_by_length(strings, permute_new);
-        // TODO: expect_same(permute_base, permute_new);
+    // Randomized tests
+    struct {
+        std::size_t length_upper_bound;
+        std::size_t iterations;
+    } fuzzy_cases[] = {
+        {10, 1000},
+        {100, 100},
+        {1000, 10},
+    };
+    std::mt19937 &generator = global_random_generator();
+    sz::string first, second;
+    for (auto fuzzy_case : fuzzy_cases) {
+        char alphabet[4] = {'a', 'c', 'g', 't'};
+        std::uniform_int_distribution<std::size_t> length_distribution(0, fuzzy_case.length_upper_bound);
+        for (std::size_t i = 0; i != fuzzy_case.iterations; ++i) {
+            std::size_t first_length = length_distribution(generator);
+            std::size_t second_length = length_distribution(generator);
+            std::generate_n(std::back_inserter(first), first_length, [&]() { return alphabet[generator() % 4]; });
+            std::generate_n(std::back_inserter(second), second_length, [&]() { return alphabet[generator() % 4]; });
+            test_distance(first, second,
+                          levenshtein_baseline(first.c_str(), first.length(), second.c_str(), second.length()));
+
+            // Try computing the distance on equal-length chunks of those strings.
+            first.resize(std::min(first_length, second_length));
+            second.resize(std::min(first_length, second_length));
+            test_distance(first, second,
+                          levenshtein_baseline(first.c_str(), first.length(), second.c_str(), second.length()));
+
+            // Discard before the next iteration.
+            first.clear();
+            second.clear();
+        }
     }
+}
+
+/**
+ *  @brief  Tests sorting functionality.
+ */
+static void test_sequence_algorithms() {
+    using strs_t = std::vector<std::string>;
+    using order_t = std::vector<std::size_t>;
 
-    // Sorting
-    if (true) {
-        std::printf("---- Sorting:\n");
-        bench_permute("std::sort", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
-            std::sort(permute.begin(), permute.end(), [&](idx_t i, idx_t j) { return strings[i] < strings[j]; });
-        });
-        expect_sorted(strings, permute_base);
-
-        bench_permute("sz_sort", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
-            sz_sequence_t array;
-            array.order = permute.data();
-            array.count = strings.size();
-            array.handle = &strings;
-            array.get_start = get_start;
-            array.get_length = get_length;
-            sz_sort(&array, nullptr);
-        });
-        expect_sorted(strings, permute_new);
-
-        bench_permute("sz_sort_introsort", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
-            sz_sequence_t array;
-            array.order = permute.data();
-            array.count = strings.size();
-            array.handle = &strings;
-            array.get_start = get_start;
-            array.get_length = get_length;
-            sz_sort_introsort(&array, (sz_sequence_comparator_t)_sz_sort_compare_less_ascii);
-        });
-        expect_sorted(strings, permute_new);
-
-        bench_permute("hybrid_sort_c", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
-            sz_sequence_t array;
-            array.order = permute.data();
-            array.count = strings.size();
-            array.handle = &strings;
-            array.get_start = get_start;
-            array.get_length = get_length;
-            hybrid_sort_c(&array);
-        });
-        expect_sorted(strings, permute_new);
-
-        bench_permute("hybrid_sort_cpp", strings, permute_new, [](strings_t const &strings, permute_t &permute) {
-            hybrid_sort_cpp(strings, permute.data());
-        });
-        expect_sorted(strings, permute_new);
-
-        std::printf("---- Stable Sorting:\n");
-        bench_permute("std::stable_sort", strings, permute_base, [](strings_t const &strings, permute_t &permute) {
-            std::stable_sort(permute.begin(), permute.end(), [&](idx_t i, idx_t j) { return strings[i] < strings[j]; });
-        });
-        expect_sorted(strings, permute_base);
-
-        bench_permute(
-            "hybrid_stable_sort_cpp",
-            strings,
-            permute_base,
-            [](strings_t const &strings, permute_t &permute) { hybrid_stable_sort_cpp(strings, permute.data()); });
-        expect_sorted(strings, permute_new);
-        expect_same(permute_base, permute_new);
+    assert_scoped(strs_t x({"a", "b", "c", "d"}), (void)0, sz::sorted_order(x) == order_t({0, 1, 2, 3}));
+    assert_scoped(strs_t x({"b", "c", "d", "a"}), (void)0, sz::sorted_order(x) == order_t({3, 0, 1, 2}));
+    assert_scoped(strs_t x({"b", "a", "d", "c"}), (void)0, sz::sorted_order(x) == order_t({1, 0, 3, 2}));
+
+    // Generate random strings of different lengths.
+    for (std::size_t dataset_size : {10, 100, 1000, 10000}) {
+        // Build the dataset.
+        strs_t dataset;
+        for (std::size_t i = 0; i != dataset_size; ++i)
+            dataset.push_back(sz::scripts::random_string(i % 32, "abcdefghijklmnopqrstuvwxyz", 26));
+
+        // Run several iterations of fuzzy tests.
+        for (std::size_t experiment_idx = 0; experiment_idx != 10; ++experiment_idx) {
+            std::shuffle(dataset.begin(), dataset.end(), global_random_generator());
+            auto order = sz::sorted_order(dataset);
+            for (std::size_t i = 1; i != dataset_size; ++i) { assert(dataset[order[i - 1]] <= dataset[order[i]]); }
+        }
     }
+}
+
+int main(int argc, char const **argv) {
+
+    // Let's greet the user nicely
+    sz_unused(argc && argv);
+    std::printf("Hi, dear tester! You look nice today!\n");
+    std::printf("- Uses AVX2: %s \n", SZ_USE_X86_AVX2 ? "yes" : "no");
+    std::printf("- Uses AVX512: %s \n", SZ_USE_X86_AVX512 ? "yes" : "no");
+    std::printf("- Uses NEON: %s \n", SZ_USE_ARM_NEON ? "yes" : "no");
+    std::printf("- Uses SVE: %s \n", SZ_USE_ARM_SVE ? "yes" : "no");
 
+    // Basic utilities
+    test_arithmetical_utilities();
+    test_memory_utilities();
+
+// Compatibility with STL
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+    test_api_readonly<std::string_view>();
+#endif
+    test_api_readonly<std::string>();
+    test_api_readonly<sz::string_view>();
+    test_api_readonly<sz::string>();
+
+    test_api_mutable<std::string>(); // Make sure the test itself is reasonable
+    test_api_mutable<sz::string>();  // The fact that this compiles is already a miracle :)
+
+    // Cover the non-STL interfaces
+    test_api_readonly_extensions<sz::string_view>();
+    test_api_readonly_extensions<sz::string>();
+    test_api_mutable_extensions();
+
+    // The string class implementation
+    test_constructors();
+    test_memory_stability_for_length(1024);
+    test_memory_stability_for_length(14);
+    test_updates();
+
+    // Advanced search operations
+    test_stl_conversion_api();
+    test_comparisons();
+    test_search();
+#if SZ_DETECT_CPP_17 && __cpp_lib_string_view
+    test_search_with_misaligned_repetitions();
+#endif
+
+    // Similarity measures and fuzzy search
+    test_levenshtein_distances();
+
+    // Sequences of strings
+    test_sequence_algorithms();
+
+    std::printf("All tests passed... Unbelievable!\n");
     return 0;
-}
\ No newline at end of file
+}
diff --git a/scripts/test.hpp b/scripts/test.hpp
new file mode 100644
index 00000000..3481a840
--- /dev/null
+++ b/scripts/test.hpp
@@ -0,0 +1,85 @@
+/**
+ *  @brief  Helper structures and functions for C++ tests.
+ */
+#pragma once
+#include <fstream>  // `std::ifstream`
+#include <iostream> // `std::cout`, `std::endl`
+#include <random>   // `std::random_device`
+#include <string>   // `std::string`
+#include <vector>   // `std::vector`
+
+namespace ashvardanian {
+namespace stringzilla {
+namespace scripts {
+
+inline std::string read_file(std::string path) {
+    std::ifstream stream(path);
+    if (!stream.is_open()) { throw std::runtime_error("Failed to open file: " + path); }
+    return std::string((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
+}
+
+inline void write_file(std::string path, std::string content) {
+    std::ofstream stream(path);
+    if (!stream.is_open()) { throw std::runtime_error("Failed to open file: " + path); }
+    stream << content;
+    stream.close();
+}
+
+inline std::mt19937 &global_random_generator() {
+    static std::random_device seed_source; // Too expensive to construct every time
+    static std::mt19937 generator(seed_source());
+    return generator;
+}
+
+inline void randomize_string(char *string, std::size_t length, char const *alphabet, std::size_t cardinality) {
+    using max_alphabet_size_t = std::uint8_t;
+    std::uniform_int_distribution<max_alphabet_size_t> distribution(1, static_cast<max_alphabet_size_t>(cardinality));
+    std::generate(string, string + length, [&]() -> char { return alphabet[distribution(global_random_generator())]; });
+}
+
+inline std::string random_string(std::size_t length, char const *alphabet, std::size_t cardinality) {
+    std::string result(length, '\0');
+    randomize_string(&result[0], length, alphabet, cardinality);
+    return result;
+}
+
+/**
+ *  @brief  Inefficient baseline Levenshtein distance computation, as implemented in most codebases.
+ *          Allocates a new matrix on every call, with rows potentially scattered around memory.
+ */
+inline std::size_t levenshtein_baseline(char const *s1, std::size_t len1, char const *s2, std::size_t len2) {
+    std::vector<std::vector<std::size_t>> dp(len1 + 1, std::vector<std::size_t>(len2 + 1));
+
+    // Initialize the borders of the matrix.
+    for (std::size_t i = 0; i <= len1; ++i) dp[i][0] = i;
+    for (std::size_t j = 0; j <= len2; ++j) dp[0][j] = j;
+
+    for (std::size_t i = 1; i <= len1; ++i) {
+        for (std::size_t j = 1; j <= len2; ++j) {
+            std::size_t cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
+            // dp[i][j] is the minimum of deletion, insertion, or substitution
+            dp[i][j] = std::min({
+                dp[i - 1][j] + 1,       // Deletion
+                dp[i][j - 1] + 1,       // Insertion
+                dp[i - 1][j - 1] + cost // Substitution
+            });
+        }
+    }
+
+    return dp[len1][len2];
+}
+
+/**
+ *  @brief  Produces a substitution cost matrix for the Needlemann-Wunsch alignment score,
+ *          that would yield the same result as the negative Levenshtein distance.
+ */
+inline std::vector<std::int8_t> unary_substitution_costs() {
+    std::vector<std::int8_t> result(256 * 256);
+    for (std::size_t i = 0; i != 256; ++i)
+        for (std::size_t j = 0; j != 256; ++j) result[i * 256 + j] = (i == j ? 0 : -1);
+    return result;
+}
+
+} // namespace scripts
+} // namespace stringzilla
+} // namespace ashvardanian
\ No newline at end of file
diff --git a/scripts/test.py b/scripts/test.py
new file mode 100644
index 00000000..172dd7f1
--- /dev/null
+++ b/scripts/test.py
@@ -0,0 +1,355 @@
+from random import choice, randint
+from string import ascii_lowercase
+from typing import Optional
+
+import numpy as np
+import pytest
+
+import stringzilla as sz
+from stringzilla import Str, Strs
+
+
+def test_library_properties():
+    assert len(sz.__version__.split(".")) == 3, "Semantic versioning must be preserved"
+    assert "serial" in sz.__capabilities__.split(","), "Serial backend must be present"
+
+
+def test_unit_construct():
+    native = "aaaaa"
+    big = Str(native)
+    assert len(big) == len(native)
+
+
+def test_unit_indexing():
+    native = "abcdef"
+    big = Str(native)
+    for i in range(len(native)):
+        assert big[i] == native[i]
+
+
+def test_unit_count():
+    native = "aaaaa"
+    big = Str(native)
+    assert big.count("a") == 5
+    assert big.count("aa") == 2
+    assert big.count("aa", allowoverlap=True) == 4
+
+
+def test_unit_contains():
+    big = Str("abcdef")
+    assert "a" in big
+    assert "ab" in big
+    assert "xxx" not in big
+
+
+def test_unit_rich_comparisons():
+    assert Str("aa") == "aa"
+    assert Str("aa") < "b"
+    s2 = Str("abb")
+    assert s2[1:] == "bb"
+    assert s2[:-1] == "ab"
+    assert s2[-1:] == "b"
+
+
+def test_unit_buffer_protocol():
+    import numpy as np
+
+    my_str = Str("hello")
+    arr = np.array(my_str)
+    assert arr.dtype == np.dtype("c")
+    assert arr.shape == (len("hello"),)
+    assert "".join([c.decode("utf-8") for c in arr.tolist()]) == "hello"
+
+
+def test_unit_split():
+    native = "token1\ntoken2\ntoken3"
+    big = Str(native)
+    assert native.splitlines() == list(big.splitlines())
+    assert native.splitlines(True) == list(big.splitlines(keeplinebreaks=True))
+    assert native.split("token3") == list(big.split("token3"))
+
+    words = sz.split(big, "\n")
+    assert len(words) == 3
+    assert str(words[0]) == "token1"
+    assert str(words[2]) == "token3"
+
+    parts = sz.split(big, "\n", keepseparator=True)
+    assert len(parts) == 3
+    assert str(parts[0]) == "token1\n"
+    assert str(parts[2]) == "token3"
+
+
+def test_unit_sequence():
+    native = "p3\np2\np1"
+    big = Str(native)
+
+    lines = big.splitlines()
+    assert [2, 1, 0] == list(lines.order())
+
+    lines.sort()
+    assert [0, 1, 2] == list(lines.order())
+    assert ["p1", "p2", "p3"] == list(lines)
+
+    # Reverse order
+    assert [2, 1, 0] == list(lines.order(reverse=True))
+    lines.sort(reverse=True)
+    assert ["p3", "p2", "p1"] == list(lines)
+
+
+def test_unit_globals():
+    """Validates that the previously unit-tested member methods are also visible as global functions."""
+
+    assert sz.find("abcdef", "bcdef") == 1
+    assert sz.find("abcdef", "x") == -1
+
+    assert sz.count("abcdef", "x") == 0
+    assert sz.count("aaaaa", "a") == 5
+    assert sz.count("aaaaa", "aa") == 2
+    assert sz.count("aaaaa", "aa", allowoverlap=True) == 4
+
+    assert sz.edit_distance("aaa", "aaa") == 0
+    assert sz.edit_distance("aaa", "bbb") == 3
+    assert sz.edit_distance("abababab", "aaaaaaaa") == 4
+    assert sz.edit_distance("abababab", "aaaaaaaa", 2) == 2
+    assert sz.edit_distance("abababab", "aaaaaaaa", bound=2) == 2
+
+
+def test_unit_len():
+    w = sz.Str("abcd")
+    assert 4 == len(w)
+
+
+def test_slice_of_split():
+    def impl(native_str):
+        native_split = native_str.split()
+        text = sz.Str(native_str)
+        split = text.split()
+        for split_idx in range(len(native_split)):
+            native_slice = native_split[split_idx:]
+            idx = split_idx
+            for word in split[split_idx:]:
+                assert str(word) == native_split[idx]
+                idx += 1
+
+    native_str = "Weebles wobble before they fall down, don't they?"
+    impl(native_str)
+    # ~5GB to overflow 32-bit sizes
+    copies = int(len(native_str) / 5e9)
+    # Eek. Cover 64-bit indices
+    impl(native_str * copies)
+
+
+def get_random_string(
+    length: Optional[int] = None,
+    variability: Optional[int] = None,
+) -> str:
+    if length is None:
+        length = randint(3, 300)
+    if variability is None:
+        variability = len(ascii_lowercase)
+    return "".join(choice(ascii_lowercase[:variability]) for _ in range(length))
+
+
+def is_equal_strings(native_strings, big_strings):
+    for native_slice, big_slice in zip(native_strings, big_strings):
+        assert (
+            native_slice == big_slice
+        ), f"Mismatch between `{native_slice}` and `{str(big_slice)}`"
+
+
+def baseline_edit_distance(s1, s2) -> int:
+    """
+    Compute the Levenshtein distance between two strings.
+    """
+    # Create a matrix of size (len(s1)+1) x (len(s2)+1)
+    matrix = np.zeros((len(s1) + 1, len(s2) + 1), dtype=int)
+
+    # Initialize the first column and first row of the matrix
+    for i in range(len(s1) + 1):
+        matrix[i, 0] = i
+    for j in range(len(s2) + 1):
+        matrix[0, j] = j
+
+    # Compute Levenshtein distance
+    for i in range(1, len(s1) + 1):
+        for j in range(1, len(s2) + 1):
+            if s1[i - 1] == s2[j - 1]:
+                cost = 0
+            else:
+                cost = 1
+            matrix[i, j] = min(
+                matrix[i - 1, j] + 1,  # Deletion
+                matrix[i, j - 1] + 1,  # Insertion
+                matrix[i - 1, j - 1] + cost,  # Substitution
+            )
+
+    # Return the Levenshtein distance
+    return matrix[len(s1), len(s2)]
+
+
+def check_identical(
+    native: str,
+    big: Str,
+    needle: Optional[str] = None,
+    check_iterators: bool = False,
+):
+    if needle is None:
+        part_offset = randint(0, len(native) - 1)
+        part_length = randint(1, len(native) - part_offset)
+        needle = native[part_offset:part_length]
+
+    present_in_native: bool = needle in native
+    present_in_big = needle in big
+    assert present_in_native == present_in_big
+    assert native.find(needle) == big.find(needle)
+    assert native.rfind(needle) == big.rfind(needle)
+    assert native.count(needle) == big.count(needle)
+
+    # Check that the `start` and `stop` positions are correctly inferred
+    len_half = len(native) // 2
+    len_quarter = len(native) // 4
+    assert native.find(needle, len_half) == big.find(needle, len_half)
+    assert native.find(needle, len_quarter, 3 * len_quarter) == big.find(
+        needle, len_quarter, 3 * len_quarter
+    )
+
+    # Check splits and other sequence operations
+    native_strings = native.split(needle)
+    big_strings: Strs = big.split(needle)
+    assert len(native_strings) == len(big_strings)
+
+    if check_iterators:
+        for i in range(len(native_strings)):
+            assert len(native_strings[i]) == len(big_strings[i])
+            assert (
+                native_strings[i] == big_strings[i]
+            ), f"Mismatch between `{native_strings[i]}` and `{str(big_strings[i])}`"
+            assert [c for c in native_strings[i]] == [c for c in big_strings[i]]
+
+    is_equal_strings(native_strings, big_strings)
+
+
+@pytest.mark.parametrize("repetitions", range(1, 10))
+def test_fuzzy_repetitions(repetitions: int):
+    native = "abcd" * repetitions
+    big = Str(native)
+
+    check_identical(native, big, "a", True)
+    check_identical(native, big, "ab", True)
+    check_identical(native, big, "abc", True)
+    check_identical(native, big, "abcd", True)
+    check_identical(native, big, "abcde", True)  # Missing pattern
+
+
+@pytest.mark.parametrize("pattern_length", [1, 2, 3, 4, 5])
+@pytest.mark.parametrize("haystack_length", range(1, 65))
+@pytest.mark.parametrize("variability", range(1, 25))
+def test_fuzzy_substrings(pattern_length: int, haystack_length: int, variability: int):
+    native = get_random_string(variability=variability, length=haystack_length)
+    big = Str(native)
+    pattern = get_random_string(variability=variability, length=pattern_length)
+    assert (pattern in native) == big.contains(
+        pattern
+    ), f"Failed to check if {pattern} at offset {native.find(pattern)} is present in {native}"
+    assert native.find(pattern) == big.find(
+        pattern
+    ), f"Failed to locate {pattern} at offset {native.find(pattern)} in {native}"
+
+
+@pytest.mark.repeat(100)
+@pytest.mark.parametrize("max_edit_distance", [150])
+def test_edit_distance_insertions(max_edit_distance: int):
+    # Create a new string by slicing and concatenating
+    def insert_char_at(s, char_to_insert, index):
+        return s[:index] + char_to_insert + s[index:]
+
+    a = get_random_string(length=20)
+    b = a
+    for i in range(max_edit_distance):
+        source_offset = randint(0, len(ascii_lowercase) - 1)
+        target_offset = randint(0, len(b) - 1)
+        b = insert_char_at(b, ascii_lowercase[source_offset], target_offset)
+        assert sz.edit_distance(a, b, bound=200) == i + 1
+
+
+@pytest.mark.repeat(30)
+@pytest.mark.parametrize("first_length", [20, 100])
+@pytest.mark.parametrize("second_length", [20, 100])
+def test_edit_distance_random(first_length: int, second_length: int):
+    a = get_random_string(length=first_length)
+    b = get_random_string(length=second_length)
+    assert sz.edit_distance(a, b) == baseline_edit_distance(a, b)
+
+
+@pytest.mark.repeat(30)
+@pytest.mark.parametrize("first_length", [20, 100])
+@pytest.mark.parametrize("second_length", [20, 100])
+def test_alignment_score_random(first_length: int, second_length: int):
+    a = get_random_string(length=first_length)
+    b = get_random_string(length=second_length)
+    character_substitutions = np.zeros((256, 256), dtype=np.int8)
+    character_substitutions.fill(-1)
+    np.fill_diagonal(character_substitutions, 0)
+
+    assert sz.alignment_score(
+        a,
+        b,
+        substitution_matrix=character_substitutions,
+        gap_score=-1,
+    ) == -baseline_edit_distance(a, b)
+
+
+@pytest.mark.parametrize("list_length", [10, 20, 30, 40, 50])
+@pytest.mark.parametrize("part_length", [5, 10])
+@pytest.mark.parametrize("variability", [2, 3])
+def test_fuzzy_sorting(list_length: int, part_length: int, variability: int):
+    native_list = [
+        get_random_string(variability=variability, length=part_length)
+        for _ in range(list_length)
+    ]
+    native_joined = ".".join(native_list)
+    big_joined = Str(native_joined)
+    big_list = big_joined.split(".")
+
+    native_ordered = sorted(native_list)
+    native_order = big_list.order()
+    for i in range(list_length):
+        assert native_ordered[i] == native_list[native_order[i]], "Order is wrong"
+        assert native_ordered[i] == str(
+            big_list[int(native_order[i])]
+        ), "Split is wrong?!"
+
+    native_list.sort()
+    big_list.sort()
+
+    assert len(native_list) == len(big_list)
+    for native_str, big_str in zip(native_list, big_list):
+        assert native_str == str(big_str), "Order is wrong"
+
+
+@pytest.mark.parametrize("list_length", [10, 20, 30, 40, 50])
+@pytest.mark.parametrize("part_length", [5, 10])
+@pytest.mark.parametrize("variability", [2, 3])
+def test_fuzzy_sorting(list_length: int, part_length: int, variability: int):
+    native_list = [
+        get_random_string(variability=variability, length=part_length)
+        for _ in range(list_length)
+    ]
+    native_joined = ".".join(native_list)
+    big_joined = Str(native_joined)
+    big_list = big_joined.split(".")
+
+    native_ordered = sorted(native_list)
+    native_order = big_list.order()
+    for i in range(list_length):
+        assert native_ordered[i] == native_list[native_order[i]], "Order is wrong"
+        assert native_ordered[i] == str(
+            big_list[int(native_order[i])]
+        ), "Split is wrong?!"
+
+    native_list.sort()
+    big_list.sort()
+
+    assert len(native_list) == len(big_list)
+    for native_str, big_str in zip(native_list, big_list):
+        assert native_str == str(big_str), "Order is wrong"
diff --git a/scripts/test_fuzzy.py b/scripts/test_fuzzy.py
deleted file mode 100644
index 694c1818..00000000
--- a/scripts/test_fuzzy.py
+++ /dev/null
@@ -1,129 +0,0 @@
-from typing import Union, Optional
-from random import choice, randint
-from string import ascii_lowercase
-
-import pytest
-
-import stringzilla as sz
-from stringzilla import Str, Strs
-
-
-def get_random_string(
-    length: Optional[int] = None, variability: Optional[int] = None
-) -> str:
-    if length is None:
-        length = randint(3, 300)
-    if variability is None:
-        variability = len(ascii_lowercase)
-    return "".join(choice(ascii_lowercase[:variability]) for _ in range(length))
-
-
-def is_equal_strings(native_strings, big_strings):
-    for native_slice, big_slice in zip(native_strings, big_strings):
-        assert (
-            native_slice == big_slice
-        ), f"Mismatch between `{native_slice}` and `{str(big_slice)}`"
-
-
-def check_identical(
-    native: str,
-    big: Str,
-    needle: Optional[str] = None,
-    check_iterators: bool = False,
-):
-    if needle is None:
-        part_offset = randint(0, len(native) - 1)
-        part_length = randint(1, len(native) - part_offset)
-        needle = native[part_offset:part_length]
-
-    present_in_native: bool = needle in native
-    present_in_big = needle in big
-    assert present_in_native == present_in_big
-    assert native.find(needle) == big.find(needle)
-    assert native.count(needle) == big.count(needle)
-
-    native_strings = native.split(needle)
-    big_strings: Strs = big.split(needle)
-    assert len(native_strings) == len(big_strings)
-
-    if check_iterators:
-        for i in range(len(native_strings)):
-            assert len(native_strings[i]) == len(big_strings[i])
-            assert (
-                native_strings[i] == big_strings[i]
-            ), f"Mismatch between `{native_strings[i]}` and `{str(big_strings[i])}`"
-            assert [c for c in native_strings[i]] == [c for c in big_strings[i]]
-
-    is_equal_strings(native_strings, big_strings)
-
-
-@pytest.mark.parametrize("repetitions", range(1, 10))
-def test_fuzzy_repetitions(repetitions: int):
-    native = "abcd" * repetitions
-    big = Str(native)
-
-    check_identical(native, big, "a", True)
-    check_identical(native, big, "ab", True)
-    check_identical(native, big, "abc", True)
-    check_identical(native, big, "abcd", True)
-    check_identical(native, big, "abcde", True)  # Missing pattern
-
-
-@pytest.mark.parametrize("pattern_length", [1, 2, 3, 4, 5])
-@pytest.mark.parametrize("haystack_length", range(1, 65))
-@pytest.mark.parametrize("variability", range(1, 25))
-def test_fuzzy_substrings(pattern_length: int, haystack_length: int, variability: int):
-    native = get_random_string(variability=variability, length=haystack_length)
-    big = Str(native)
-    pattern = get_random_string(variability=variability, length=pattern_length)
-    assert (pattern in native) == big.contains(
-        pattern
-    ), f"Failed to check if {pattern} at offset {native.find(pattern)} is present in {native}"
-    assert native.find(pattern) == big.find(
-        pattern
-    ), f"Failed to locate {pattern} at offset {native.find(pattern)} in {native}"
-
-
-@pytest.mark.parametrize("iterations", range(100))
-@pytest.mark.parametrize("max_edit_distance", [150])
-def test_levenstein(iterations: int, max_edit_distance: int):
-    # Create a new string by slicing and concatenating
-    def insert_char_at(s, char_to_insert, index):
-        return s[:index] + char_to_insert + s[index:]
-
-    for _ in range(iterations):
-        a = get_random_string(length=20)
-        b = a
-        for i in range(max_edit_distance):
-            source_offset = randint(0, len(ascii_lowercase) - 1)
-            target_offset = randint(0, len(b) - 1)
-            b = insert_char_at(b, ascii_lowercase[source_offset], target_offset)
-            assert sz.levenstein(a, b, 200) == i + 1
-
-
-@pytest.mark.parametrize("list_length", [10, 20, 30, 40, 50])
-@pytest.mark.parametrize("part_length", [5, 10])
-@pytest.mark.parametrize("variability", [2, 3])
-def test_fuzzy_sorting(list_length: int, part_length: int, variability: int):
-    native_list = [
-        get_random_string(variability=variability, length=part_length)
-        for _ in range(list_length)
-    ]
-    native_joined = ".".join(native_list)
-    big_joined = Str(native_joined)
-    big_list = big_joined.split(".")
-
-    native_ordered = sorted(native_list)
-    native_order = big_list.order()
-    for i in range(list_length):
-        assert native_ordered[i] == native_list[native_order[i]], "Order is wrong"
-        assert native_ordered[i] == str(
-            big_list[int(native_order[i])]
-        ), "Split is wrong?!"
-
-    native_list.sort()
-    big_list.sort()
-
-    assert len(native_list) == len(big_list)
-    for native_str, big_str in zip(native_list, big_list):
-        assert native_str == str(big_str), "Order is wrong"
diff --git a/scripts/test_units.py b/scripts/test_units.py
deleted file mode 100644
index a2f985a7..00000000
--- a/scripts/test_units.py
+++ /dev/null
@@ -1,105 +0,0 @@
-from typing import Union, Optional
-from random import choice, randint
-from string import ascii_lowercase
-
-import pytest
-
-import stringzilla as sz
-from stringzilla import Str, Strs
-
-
-def test_unit_construct():
-    native = "aaaaa"
-    big = Str(native)
-    assert len(big) == len(native)
-
-
-def test_unit_indexing():
-    native = "abcdef"
-    big = Str(native)
-    for i in range(len(native)):
-        assert big[i] == native[i]
-
-
-def test_unit_count():
-    native = "aaaaa"
-    big = Str(native)
-    assert big.count("a") == 5
-    assert big.count("aa") == 2
-    assert big.count("aa", allowoverlap=True) == 4
-
-
-def test_unit_contains():
-    big = Str("abcdef")
-    assert "a" in big
-    assert "ab" in big
-    assert "xxx" not in big
-
-
-def test_unit_rich_comparisons():
-    assert Str("aa") == "aa"
-    assert Str("aa") < "b"
-    assert Str("abb")[1:] == "bb"
-
-
-def test_unit_buffer_protocol():
-    import numpy as np
-
-    my_str = Str("hello")
-    arr = np.array(my_str)
-    assert arr.dtype == np.dtype("c")
-    assert arr.shape == (len("hello"),)
-    assert "".join([c.decode("utf-8") for c in arr.tolist()]) == "hello"
-
-
-def test_unit_split():
-    native = "token1\ntoken2\ntoken3"
-    big = Str(native)
-    assert native.splitlines() == list(big.splitlines())
-    assert native.splitlines(True) == list(big.splitlines(keeplinebreaks=True))
-    assert native.split("token3") == list(big.split("token3"))
-
-    words = sz.split(big, "\n")
-    assert len(words) == 3
-    assert str(words[0]) == "token1"
-    assert str(words[2]) == "token3"
-
-    parts = sz.split(big, "\n", keepseparator=True)
-    assert len(parts) == 3
-    assert str(parts[0]) == "token1\n"
-    assert str(parts[2]) == "token3"
-
-
-def test_unit_sequence():
-    native = "p3\np2\np1"
-    big = Str(native)
-
-    lines = big.splitlines()
-    assert [2, 1, 0] == list(lines.order())
-
-    lines.sort()
-    assert [0, 1, 2] == list(lines.order())
-    assert ["p1", "p2", "p3"] == list(lines)
-
-    # Reverse order
-    assert [2, 1, 0] == list(lines.order(reverse=True))
-    lines.sort(reverse=True)
-    assert ["p3", "p2", "p1"] == list(lines)
-
-
-def test_unit_globals():
-    """Validates that the previously unit-tested member methods are also visible as global functions."""
-
-    assert sz.find("abcdef", "bcdef") == 1
-    assert sz.find("abcdef", "x") == -1
-
-    assert sz.count("abcdef", "x") == 0
-    assert sz.count("aaaaa", "a") == 5
-    assert sz.count("aaaaa", "aa") == 2
-    assert sz.count("aaaaa", "aa", allowoverlap=True) == 4
-
-    assert sz.levenstein("aaa", "aaa") == 0
-    assert sz.levenstein("aaa", "bbb") == 3
-    assert sz.levenstein("abababab", "aaaaaaaa") == 4
-    assert sz.levenstein("abababab", "aaaaaaaa", 2) == 2
-    assert sz.levenstein("abababab", "aaaaaaaa", bound=2) == 2