Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
18f78b2
Initial adaptive anchor implementation
ZeroMemes Jul 31, 2026
f321118
Move scanner implementations to `src/scanner`
ZeroMemes Jul 31, 2026
38a0484
Move `system_info` implementations to `src/system`
ZeroMemes Jul 31, 2026
cf59c2c
Move `process.hpp` implementations to `src/process`
ZeroMemes Jul 31, 2026
1a0ebb4
More scanner refactoring
ZeroMemes Aug 1, 2026
6e3161a
Finish source refactoring
ZeroMemes Aug 1, 2026
050937f
Actually done with the refactoring
ZeroMemes Aug 1, 2026
1892a22
Increase impl buffer size in `scan_context`
ZeroMemes Aug 1, 2026
9c6de59
Fix `get_optimal_byte` implementation
ZeroMemes Aug 1, 2026
c015612
Select optimal pair based on signature-local byte occurrences
ZeroMemes Aug 1, 2026
d49d895
Consistency
ZeroMemes Aug 1, 2026
2c0e8f7
Fix ARM compile
ZeroMemes Aug 1, 2026
a7f44e1
Fallback to `Single` when no fully masked anchor is available
ZeroMemes Aug 2, 2026
b00002f
Remove error for signature missing masked byte
ZeroMemes Aug 2, 2026
39bb635
Allow empty signatures to be valid
ZeroMemes Aug 2, 2026
287ea90
Include `Search` mode in Scanner test
ZeroMemes Aug 2, 2026
442bb1f
Clean up single impl helpers
ZeroMemes Aug 2, 2026
b77279a
Save a massive 8 bytes
ZeroMemes Aug 2, 2026
b1a7c58
Add `hat::detail::is_supported` and avoid undefined externals
ZeroMemes Aug 9, 2026
634e32e
Add const/pure attribute macros
ZeroMemes Aug 9, 2026
ae05eca
Rename `is_defined` to `has_impl`
ZeroMemes Aug 11, 2026
a1a16d9
Add missing `<tuple>` include in `utils.cpp`
ZeroMemes Aug 23, 2026
ac6c186
Remove const qualifier from impl deleter
ZeroMemes Aug 23, 2026
24b1b30
Dynamic benchmark initialization via scanner registry
ZeroMemes Aug 23, 2026
2dcbd1f
Remove non-existent benchmark
ZeroMemes Aug 23, 2026
de84d68
Use single scanner when alignment is not less than vector width
ZeroMemes Aug 27, 2026
dc32239
Run benchmark workflow on Linux
ZeroMemes Aug 27, 2026
108f61c
Add AVX2 feature option
ZeroMemes Sep 2, 2026
c456f80
Set CMake workflow paths
ZeroMemes Sep 2, 2026
19708c2
Fix avx2 feature description in README
ZeroMemes Sep 2, 2026
490e9bb
Update benchmarks
ZeroMemes Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,41 @@ name: Benchmark
on: workflow_dispatch

jobs:
windows:
linux:
strategy:
matrix:
toolset: [ v145, ClangCL ]
runs-on: windows-11-vs2026-arm
target: [ x64, ARM64 ]
cxx_standard: [ 23 ]
compiler:
- { pkg: clang, exe: clang++, version: 22 }
include:
- target: x64
os: ubuntu-26.04
- target: ARM64
os: ubuntu-26.04-arm
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v6

- name: CPM Cache
uses: actions/cache@v5
with:
path: ${{github.workspace}}/.cpmcache
key: cpm-${{runner.os}}-ARM64-${{hashFiles('test/CMakeLists.txt')}}
key: cpm-${{runner.os}}-${{hashFiles('test/CMakeLists.txt')}}
restore-keys: |
cpm-${{runner.os}}-ARM64-
cpm-${{runner.os}}-

- name: Check Hardware
run: (Get-CimInstance Win32_Processor).Name

- name: Locate Visual Studio
run: |
$vsInstall = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath
echo "VS_INSTALL_DIR=$vsInstall" >> $env:GITHUB_ENV
run: lscpu | grep "Model name"

- name: Configure
run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=23 -DLIBHAT_SHARED_C_LIB=ON -DLIBHAT_TESTING_SDE=OFF -A ARM64 -T ${{matrix.toolset}}
env:
CXX: ${{matrix.compiler.exe}}-${{matrix.compiler.version}}
run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=Release -DLIBHAT_TESTING_SDE=OFF

- name: Build
run: cmake --build ${{github.workspace}}/build -j 4 --config Release --target libhat_benchmark_compare_impl libhat_benchmark_chromium
run: cmake --build ${{github.workspace}}/build -j 4 --target libhat_benchmark_compare libhat_benchmark_chromium

- name: Test
working-directory: ${{github.workspace}}/build
shell: cmd
run: |
call "${{env.VS_INSTALL_DIR}}\VC\Auxiliary\Build\vcvarsarm64.bat"
ctest --verbose -C Release -R "(libhat_benchmark_compare_impl|libhat_benchmark_chromium)"
run: ctest --verbose -C Release -R "(libhat_benchmark_compare|libhat_benchmark_chromium)"
10 changes: 10 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ name: CMake
on:
push:
branches: [ "master" ]
paths:
- 'bindings/c/**'
- 'include/**'
- 'src/**'
- 'test/**'
pull_request:
branches: [ "master" ]
paths:
- 'bindings/c/**'
- 'include/**'
- 'src/**'
- 'test/**'

env:
BUILD_TYPE: Debug
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ option(LIBHAT_TESTING_SAMPLE_BIN "Enables tests that require downloading a large

# Library Feature Options
option(LIBHAT_FEATURE_SSE "Enables SSE scanning, has no effect if the target isn't x86 or x86_64" ON)
option(LIBHAT_FEATURE_AVX2 "Enables AVX2 scanning, has no effect if the target isn't x86_64" ON)
option(LIBHAT_FEATURE_AVX512 "Enables AVX512 scanning, has no effect if the target isn't x86_64" ON)
option(LIBHAT_HINT_X86_64 "Enables support for the x86_64 scan hint, requires a small (2KB) data table" ON)
option(LIBHAT_HINT_AARCH64 "Enables support for the aarch64 scan hint, requires a small (2KB) data table" ON)
Expand Down Expand Up @@ -89,6 +90,7 @@ endif()

target_compile_definitions(libhat PUBLIC
"$<$<BOOL:${LIBHAT_FEATURE_SSE}>:LIBHAT_FEATURE_SSE>"
"$<$<BOOL:${LIBHAT_FEATURE_AVX2}>:LIBHAT_FEATURE_AVX2>"
"$<$<BOOL:${LIBHAT_FEATURE_AVX512}>:LIBHAT_FEATURE_AVX512>"
"$<$<BOOL:${LIBHAT_HINT_X86_64}>:LIBHAT_HINT_X86_64>"
"$<$<BOOL:${LIBHAT_HINT_AARCH64}>:LIBHAT_HINT_AARCH64>"
Expand Down
71 changes: 43 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ or `x86_64`, enabling this option has no effect.

CPU features: `sse4.1`

### `LIBHAT_FEATURE_AVX2`

Enables support for the AVX2 vectorized find_pattern implementation. If compile-time support is not present
(`-march` or `/ARCH`), runtime support is validated through `cpuid`. If the target architecture is not `x86`
or `x86_64`, enabling this option has no effect.

CPU features: `avx` `avx2` `bmi`

### `LIBHAT_FEATURE_AVX512`

Enables support for the AVX512 (BW+F) vectorized find_pattern implementation. If compile-time support is not present
Expand Down Expand Up @@ -150,30 +158,37 @@ The full source code is available [here](test/benchmark/Compare.cpp).
---------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations bytes_per_second
---------------------------------------------------------------------------------------------------
BM_Throughput_libhat/4MiB 67015 ns 66389 ns 82139 58.2888Gi/s
BM_Throughput_libhat/16MiB 311783 ns 310116 ns 18088 50.1151Gi/s
BM_Throughput_libhat/128MiB 5371248 ns 5355461 ns 1062 23.2721Gi/s
BM_Throughput_libhat/256MiB 10871393 ns 10787260 ns 520 22.9961Gi/s

BM_Throughput_std_search/4MiB 1372621 ns 1366807 ns 4104 2.84583Gi/s
BM_Throughput_std_search/16MiB 5412159 ns 5385316 ns 1030 2.88702Gi/s
BM_Throughput_std_search/128MiB 43333000 ns 43120155 ns 129 2.88464Gi/s
BM_Throughput_std_search/256MiB 86809503 ns 86181641 ns 64 2.87987Gi/s

BM_Throughput_std_find_std_equal/4MiB 189240 ns 189051 ns 29506 20.6418Gi/s
BM_Throughput_std_find_std_equal/16MiB 861429 ns 857686 ns 6613 18.1385Gi/s
BM_Throughput_std_find_std_equal/128MiB 9384011 ns 9385575 ns 591 13.3205Gi/s
BM_Throughput_std_find_std_equal/256MiB 18979502 ns 18939394 ns 297 13.1721Gi/s

BM_Throughput_UC1/4MiB 1742814 ns 1732316 ns 3202 2.24135Gi/s
BM_Throughput_UC1/16MiB 6983502 ns 6909938 ns 805 2.23742Gi/s
BM_Throughput_UC1/128MiB 55866828 ns 55847772 ns 101 2.23746Gi/s
BM_Throughput_UC1/256MiB 111606018 ns 111250000 ns 50 2.24002Gi/s

BM_Throughput_UC2/4MiB 4080670 ns 4068654 ns 1371 980.231Mi/s
BM_Throughput_UC2/16MiB 16422699 ns 16314338 ns 340 974.261Mi/s
BM_Throughput_UC2/128MiB 132357452 ns 132440476 ns 42 967.078Mi/s
BM_Throughput_UC2/256MiB 265336400 ns 264880952 ns 21 964.813Mi/s
libhat+AVX2/4MiB 70877 ns 70998 ns 79007 55.1129Gi/s
libhat+AVX2/8MiB 142373 ns 141935 ns 38750 54.8734Gi/s
libhat+AVX2/16MiB 310238 ns 308405 ns 17783 50.3645Gi/s
libhat+AVX2/32MiB 1002988 ns 999485 ns 5581 31.1569Gi/s
libhat+AVX2/64MiB 2452875 ns 2449913 ns 2296 25.4803Gi/s
libhat+AVX2/128MiB 5223758 ns 5183858 ns 1064 23.9291Gi/s
libhat+AVX2/256MiB 10713488 ns 10637019 ns 520 23.3351Gi/s

std::search/4MiB 1348946 ns 1342598 ns 4178 2.89578Gi/s
std::search/8MiB 2690336 ns 2688011 ns 2081 2.90391Gi/s
std::search/16MiB 5383973 ns 5353287 ns 1042 2.90213Gi/s
std::search/32MiB 10807674 ns 10715996 ns 522 2.89146Gi/s
std::search/64MiB 21520222 ns 21454327 ns 260 2.90425Gi/s
std::search/128MiB 43072097 ns 42999031 ns 129 2.90211Gi/s
std::search/256MiB 86326666 ns 85817308 ns 65 2.89598Gi/s

csgosimple/4MiB 1704462 ns 1695994 ns 3289 2.29178Gi/s
csgosimple/8MiB 3437443 ns 3420064 ns 1631 2.27277Gi/s
csgosimple/16MiB 6867328 ns 6835938 ns 816 2.27527Gi/s
csgosimple/32MiB 13715049 ns 13686131 ns 411 2.27852Gi/s
csgosimple/64MiB 27225719 ns 27210366 ns 205 2.29562Gi/s
csgosimple/128MiB 54692310 ns 54227941 ns 102 2.28551Gi/s
csgosimple/256MiB 108922873 ns 108762255 ns 51 2.29520Gi/s

learn_more/4MiB 4091135 ns 4090909 ns 1375 977.724Mi/s
learn_more/8MiB 8160467 ns 8143248 ns 685 980.336Mi/s
learn_more/16MiB 16449846 ns 16403959 ns 341 972.653Mi/s
learn_more/32MiB 33114440 ns 33099112 ns 169 966.346Mi/s
learn_more/64MiB 66329900 ns 65848214 ns 84 964.874Mi/s
learn_more/128MiB 133070590 ns 132812500 ns 42 961.895Mi/s
learn_more/256MiB 265652538 ns 264880952 ns 21 963.665Mi/s
```

Using the appropriate configuration, libhat is able to maintain its high throughput when searching
Expand Down Expand Up @@ -237,10 +252,10 @@ subrange `s` for which all the following conditions are met:
- `s[2] == 0x12`
- `s[3] & 0x0F == 0x03`

As a scanning optimization, all patterns are required to have at least one fully masked byte. Attempting to find a
pattern that does not meet this requirement will result in undefined behavior. Additionally, it is recommended
(but not required) that patterns contain at least 2 consecutive fully masked bytes, as this will greatly speed
up the vectorized scanning algorithms.
As a scanning optimization, all patterns are recommended to have at least one fully masked byte. Attempting to find a
pattern that does not meet this requirement results in significant performance degredation. Additionally, it is
recommended (but not required) that patterns contain at least 2 consecutive fully masked bytes, as this will greatly
speed up the vectorized scanning algorithms.
- `?1 02` is allowed
- `?? 02` is allowed
- `01 02` is allowed (*and recommended*)
Expand Down
2 changes: 0 additions & 2 deletions bindings/c/include/libhat/c/libhat.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ extern "C" {
typedef enum libhat_status {
libhat_success, // The operation was successful
libhat_err_unknown,
libhat_err_sig_missing_masked_byte,
libhat_err_sig_element_parse_error,
libhat_err_sig_empty_signature,
libhat_err_sig_expected_wildcard,
libhat_err_sig_invalid_token_length,
libhat_err_invalid_argument_value,
Expand Down
17 changes: 3 additions & 14 deletions bindings/c/src/libhat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ LIBHAT_API const char* libhat_status_to_string(const libhat_status status) {
switch (status) {
STATUS_CASE(libhat_success);
STATUS_CASE(libhat_err_unknown);
STATUS_CASE(libhat_err_sig_missing_masked_byte);
STATUS_CASE(libhat_err_sig_element_parse_error);
STATUS_CASE(libhat_err_sig_empty_signature);
STATUS_CASE(libhat_err_sig_expected_wildcard);
STATUS_CASE(libhat_err_sig_invalid_token_length);
STATUS_CASE(libhat_err_invalid_argument_value);
Expand All @@ -113,9 +111,7 @@ LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, const
*signatureOut = nullptr;
switch (result.error()) {
using enum hat::signature_error;
case missing_masked_byte: return libhat_err_sig_missing_masked_byte;
case element_parse_error: return libhat_err_sig_element_parse_error;
case empty_signature: return libhat_err_sig_empty_signature;
case expected_wildcard: return libhat_err_sig_expected_wildcard;
case invalid_token_length: return libhat_err_sig_invalid_token_length;
}
Expand All @@ -137,21 +133,14 @@ LIBHAT_API libhat_status libhat_create_signature(
if (size && (!bytes || !mask)) {
return libhat_err_invalid_argument_value;
}
if (!size) {
return libhat_err_sig_empty_signature;
}

hat::signature signature{};
bool containsByte = false;
signature.reserve(size);
signature.resize(size);
for (size_t i{}; i < size; i++) {
containsByte |= signature.emplace_back(
signature[i] = {
static_cast<std::byte>(bytes[i]),
static_cast<std::byte>(mask[i])
).all();
}
if (!containsByte) {
return libhat_err_sig_missing_masked_byte;
};
}
*signatureOut = new libhat_signature{std::move(signature)};
return libhat_success;
Expand Down
8 changes: 8 additions & 0 deletions include/libhat/defines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@
#define LIBHAT_TARGET(arch)
#endif

#if defined(__GNUC__) || defined(__clang__)
#define LIBHAT_PURE __attribute__((pure))
#define LIBHAT_CONST __attribute__((const))
#else
#define LIBHAT_PURE
#define LIBHAT_CONST
#endif

#if __has_cpp_attribute(no_unique_address)
#define LIBHAT_NO_UNIQUE_ADDRESS [[no_unique_address]]
#elif __has_cpp_attribute(msvc::no_unique_address)
Expand Down
Loading
Loading