Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
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_)(const 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