Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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