Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 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
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
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ jobs:
run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=23 -DLIBHAT_SHARED_C_LIB=ON -DLIBHAT_TESTING_SDE=OFF -A ARM64 -T ${{matrix.toolset}}

- 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 --config Release --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)"
ctest --verbose -C Release -R "(libhat_benchmark_compare|libhat_benchmark_chromium)"
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
163 changes: 79 additions & 84 deletions include/libhat/scanner.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,42 +141,72 @@ namespace hat::detail {

using scan_function_t = const_scan_result(*)(const std::byte* begin, const std::byte* end, const scan_context& context);

struct scanner_context {
std::size_t vectorSize{};
};

enum class scan_mode {
Auto, // Picks a mode at runtime
Search, // std::search
Single, // std::find + std::equal
SSE, // x86/x64 SSE 4.1
AVX2, // x86/x64 AVX2
AVX512, // x64 AVX512
Neon, // ARMv7+ Neon
};

bool is_supported(scan_mode mode);

class scan_context {
static constexpr size_t impl_buffer_size = 48;
static constexpr size_t impl_buffer_align = alignof(std::max_align_t);
public:
signature_view signature{};
scan_function_t scanner{};
scan_alignment alignment{};
scan_hint hints{};
std::size_t cmpIndex{};
std::optional<std::size_t> pairIndex{};
scan_context(const scan_context&) = delete;
scan_context(scan_context&&) = delete;
scan_context& operator=(const scan_context&) = delete;
scan_context& operator=(scan_context&&) = delete;

constexpr ~scan_context() {
if (impl_deleter_) {
impl_deleter_(impl_buffer_.data());
impl_deleter_ = nullptr;
}
}

[[nodiscard]] constexpr signature_view signature() const {
return signature_;
}

template<typename T, typename... Args>
void emplace(Args&&... args);

template<typename T>
[[nodiscard]] const T& get() const;

[[nodiscard]] constexpr const_scan_result scan(const std::byte* begin, const std::byte* end) const {
if (signature.size() > static_cast<std::size_t>(std::distance(begin, end))) LIBHAT_UNLIKELY {
if (signature_.empty()) LIBHAT_UNLIKELY {
return begin;
}
if (signature_.size() > static_cast<std::size_t>(std::distance(begin, end))) LIBHAT_UNLIKELY {
return {};
}
return this->scanner(begin, end, *this);
return scanner_(begin, end, *this);
}

void apply_hints(const scanner_context&);

template<scan_mode mode = scan_mode::Auto>
static constexpr scan_context create(signature_view signature, scan_alignment alignment, scan_hint hints);

template<typename T, typename... Args>
scan_context(const signature_view signature, const scan_function_t scanner, std::type_identity<T>, Args&&... args)
: signature_(signature), scanner_(scanner)
{
emplace<T>(std::forward<Args>(args)...);
}

constexpr scan_context(const signature_view signature, const scan_function_t scanner)
: signature_(signature), scanner_(scanner) {}

private:
scan_context() = default;
signature_view signature_{};
scan_function_t scanner_{};
void(*impl_deleter_)(void*){};
alignas(impl_buffer_align) std::array<std::byte, impl_buffer_size> impl_buffer_;
};

LIBHAT_FORCEINLINE constexpr auto to_stride(const scan_alignment alignment) {
Expand All @@ -202,77 +232,48 @@ namespace hat::detail {
return std::assume_aligned<alignment>(ptr);
}

struct scan_parameters {
signature_view signature{};
scan_alignment alignment{};
scan_hint hints{};
};

template<scan_mode>
scan_function_t resolve_scanner(scan_context&);
scan_context create_context(const scan_parameters&);

template<>
scan_function_t resolve_scanner<scan_mode::Auto>(scan_context&);
scan_context create_context<scan_mode::Auto>(const scan_parameters&);

template<scan_alignment alignment>
const_scan_result find_pattern_single(const std::byte* begin, const std::byte* end, const scan_context& context) {
const_scan_result find_pattern_search(const std::byte* begin, const std::byte* end, const scan_context& context) {
static constexpr auto stride = alignment_stride<alignment>;
const auto signature = context.signature;
const auto cmpByte = *signature[context.cmpIndex];

const auto scanBegin = align_up<stride>(begin) + context.cmpIndex;
const auto scanEnd = align_up<stride>(end - signature.size() + 1) + context.cmpIndex;

if (scanBegin >= scanEnd) {
return nullptr;
}
const auto sig = context.signature();
const auto scanBegin = align_up<stride>(begin);
const auto scanEnd = align_up<stride>(end - sig.size() + 1);

// intentionally kept simple/inefficient since this will only be used for small buffers
for (auto i = scanBegin; i != scanEnd; i += stride) {
if (*i == cmpByte) {
const auto start = i - context.cmpIndex;
const auto match = std::equal(signature.begin(), signature.end(), start);
if (match) LIBHAT_UNLIKELY {
return start;
}
if (std::equal(sig.begin(), sig.end(), i)) {
return i;
}
}

return nullptr;
}

template<>
constexpr const_scan_result find_pattern_single<scan_alignment::X1>(const std::byte* begin, const std::byte* end, const scan_context& context) {
const auto signature = context.signature;
const auto firstByte = *signature[context.cmpIndex];
const auto scanEnd = end - signature.size() + 1 + context.cmpIndex;

for (auto i = begin + context.cmpIndex; i != scanEnd; i++) {
// Use std::find to efficiently find the first byte
if LIBHAT_IF_CONSTEVAL {
i = std::find(i, scanEnd, firstByte);
if (i == scanEnd) LIBHAT_UNLIKELY break;
} else {
#ifndef _MSC_VER
i = static_cast<const std::byte*>(
std::memchr(i, static_cast<unsigned char>(firstByte), static_cast<std::size_t>(scanEnd - i)));
if (!i) LIBHAT_UNLIKELY break;
#elif __cpp_lib_execution >= 201902L
i = std::find(std::execution::unseq, i, scanEnd, firstByte);
if (i == scanEnd) LIBHAT_UNLIKELY break;
#else
i = std::find(i, scanEnd, firstByte);
if (i == scanEnd) LIBHAT_UNLIKELY break;
#endif
}
const auto start = i - context.cmpIndex;
const auto match = std::equal(signature.begin(), signature.end(), start);
if (match) LIBHAT_UNLIKELY {
return start;
}
}
return nullptr;
constexpr const_scan_result find_pattern_search<scan_alignment::X1>(const std::byte* begin, const std::byte* end, const scan_context& context) {
const auto sig = context.signature();
const auto it = std::search(begin, end, sig.begin(), sig.end());
return it != end ? it : nullptr;
}

template<>
constexpr scan_function_t resolve_scanner<scan_mode::Single>(scan_context& context) {
switch (context.alignment) {
case scan_alignment::X1: return &find_pattern_single<scan_alignment::X1>;
case scan_alignment::X4: return &find_pattern_single<scan_alignment::X4>;
case scan_alignment::X16: return &find_pattern_single<scan_alignment::X16>;
inline scan_context create_context<scan_mode::Search>(const scan_parameters& params) {
switch (params.alignment) {
case scan_alignment::X1: return {params.signature, &find_pattern_search<scan_alignment::X1>};
case scan_alignment::X4: return {params.signature, &find_pattern_search<scan_alignment::X4>};
case scan_alignment::X16: return {params.signature, &find_pattern_search<scan_alignment::X16>};
}
LIBHAT_UNREACHABLE();
}
Expand All @@ -283,25 +284,19 @@ namespace hat::detail {

template<scan_mode mode>
constexpr scan_context scan_context::create(const signature_view signature, const scan_alignment alignment, const scan_hint hints) {
std::size_t cmpIndex{};
for (const auto& elem : signature) {
if (elem.all()) {
break;
}
cmpIndex++;
}

scan_context ctx{};
ctx.signature = signature;
ctx.alignment = alignment;
ctx.hints = hints;
ctx.cmpIndex = cmpIndex;
const scan_parameters params{
.signature = signature,
.alignment = alignment,
.hints = hints,
};
if LIBHAT_IF_CONSTEVAL {
ctx.scanner = resolve_scanner<scan_mode::Single>(ctx);
if (alignment != scan_alignment::X1) {
std::abort();
}
return {signature, &find_pattern_search<scan_alignment::X1>};
} else {
ctx.scanner = resolve_scanner<mode>(ctx);
return create_context<mode>(params);
}
return ctx;
}
}

Expand Down
25 changes: 4 additions & 21 deletions include/libhat/signature.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@ LIBHAT_EXPORT namespace hat {
using fixed_signature = std::array<signature_element, N>;

enum class signature_error {
missing_masked_byte,
element_parse_error,
empty_signature,
expected_wildcard,
invalid_token_length,
};
Expand All @@ -105,11 +103,8 @@ LIBHAT_EXPORT namespace hat {
}

/// Convert raw byte storage into a signature
[[nodiscard]] LIBHAT_CONSTEXPR_RESULT result<signature, signature_error> bytes_to_signature(std::span<const std::byte> bytes) {
if (bytes.empty()) {
return result_error{signature_error::empty_signature};
}
return signature{bytes.begin(), bytes.end()};
[[nodiscard]] constexpr signature bytes_to_signature(std::span<const std::byte> bytes) {
return {bytes.begin(), bytes.end()};
}

template<typename T>
Expand All @@ -124,11 +119,7 @@ LIBHAT_EXPORT namespace hat {
}

template<typename Char>
[[nodiscard]] LIBHAT_CONSTEXPR_RESULT result<signature, signature_error> string_to_signature(std::basic_string_view<Char> str) {
if (str.empty()) {
return result_error{signature_error::empty_signature};
}

[[nodiscard]] constexpr signature string_to_signature(std::basic_string_view<Char> str) {
signature result;
result.resize(str.size() * sizeof(Char));

Expand All @@ -143,7 +134,7 @@ LIBHAT_EXPORT namespace hat {
}

template<typename Char>
[[nodiscard]] LIBHAT_CONSTEXPR_RESULT result<signature, signature_error> string_to_signature(std::basic_string<Char> str) {
[[nodiscard]] constexpr signature string_to_signature(std::basic_string<Char> str) {
return string_to_signature(std::basic_string_view<Char>{str});
}

Expand Down Expand Up @@ -171,7 +162,6 @@ LIBHAT_EXPORT namespace hat {

[[nodiscard]] LIBHAT_CONSTEXPR_RESULT result<std::size_t, signature_error> parse_signature_to(std::output_iterator<signature_element> auto out, const std::string_view str) {
std::size_t written = 0;
bool containsByte = false;

for (auto&& sub : str | std::views::split(' ')) {
const std::string_view word{sub.begin(), sub.end()};
Expand All @@ -194,7 +184,6 @@ LIBHAT_EXPORT namespace hat {
if (element) {
*out++ = *element;
written++;
containsByte |= element->all();
} else {
return result_error{signature_error::element_parse_error};
}
Expand All @@ -205,12 +194,6 @@ LIBHAT_EXPORT namespace hat {
}
}
}
if (written == 0) {
return result_error{signature_error::empty_signature};
}
if (!containsByte) {
return result_error{signature_error::missing_masked_byte};
}
return written;
}

Expand Down
2 changes: 1 addition & 1 deletion include/libhat/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,5 @@ LIBHAT_EXPORT namespace hat {

LIBHAT_EXPORT namespace hat {

const system_info_impl& get_system();
LIBHAT_PURE const system_info_impl& get_system();
}
Loading