From b8e577b5bb06447cc8d275d0b899e57680fac869 Mon Sep 17 00:00:00 2001 From: amir Date: Wed, 15 Apr 2026 21:03:54 +0100 Subject: [PATCH] Porting IO Ring to linux by implementing io_uring --- .gitignore | 2 + README.md | 12 +- base.h | 44 +- binaries/changelog.txt | 2 +- compile_commands.json | 2 +- file_hasher | Bin 0 -> 245536 bytes file_hasher.c | 13 +- io_uring_test | Bin 0 -> 27808 bytes io_uring_test.c | 454 +++++++++++++++++++ platform.c | 995 ++++++++++++++++++++++++++++++----------- 10 files changed, 1229 insertions(+), 295 deletions(-) create mode 100644 file_hasher create mode 100644 io_uring_test create mode 100644 io_uring_test.c diff --git a/.gitignore b/.gitignore index 96c65c4..19b273a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ Binaries/file_hashes.txt file_list.txt temp_code.c /.cache/clangd/index +/file_hasher +/io_uring_test diff --git a/README.md b/README.md index 697a40d..719ef96 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,20 @@ Collects some metadata and hashes files. #### Release: clang-cl /O3 file_hasher.c xxh_x86dispatch.c -Note: MinGW does not provide IO Ring headers yet, to fix that include ioringapi.c, this will dynamically load all the functions and define all the symbols necessary to replace the official header. +Note: MinGW does not provide IO Ring headers yet, to fix that include ioringapi.c, this will dynamically load all the functions and define all the symbols necessary to replace the official header. clang -O3 file_hasher.c xxh_x86dispatch.c -o file_hasher gcc -O3 file_hasher.c xxh_x86dispatch.c -o file_hasher #### Debug: -clang-cl /Zi /Od file_hasher.c xxh_x86dispatch.c +clang-cl /Zi /Od file_hasher.c xxh_x86dispatch.c clang -g -O0 file_hasher.c xxh_x86dispatch.c -o file_hasher gcc -g -O0 file_hasher.c xxh_x86dispatch.c -o file_hasher ### Linux: #### Release: -clang -O3 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher -gcc -O3 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher +clang -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher +gcc -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher #### Debug: -clang -g -O0 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher -gcc -g -O0 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher +clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher +gcc -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher diff --git a/base.h b/base.h index 4bd4ca2..4439968 100644 --- a/base.h +++ b/base.h @@ -1,23 +1,11 @@ #pragma once #define _CRT_SECURE_NO_WARNINGS -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #if defined(_WIN32) || defined(_WIN64) -// #define PLATFORM_WINDOWS 1 -// #define WIN32_LEAN_AND_MEAN -// #define NTDDI_VERSION NTDDI_WIN11 -// -// #pragma comment(lib, "kernel32.Lib") + +#if defined(_MSC_VER) +#pragma comment(lib, "advapi32.lib") +#endif #include #include @@ -29,20 +17,36 @@ #include #include -#if defined(_MSC_VER) -#pragma comment(lib, "advapi32.lib") +#elif defined(__linux__) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE #endif -#define strdup _strdup -#else #include #include +#include #include #include +#include #include #include +#include +#include #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + /* ------------------------------------------------------------ Base types ------------------------------------------------------------ */ diff --git a/binaries/changelog.txt b/binaries/changelog.txt index 0db7f6c..e2b14d8 100644 --- a/binaries/changelog.txt +++ b/binaries/changelog.txt @@ -50,7 +50,7 @@ Fixing user prompt parsing Reorganising the code Improving the scan function -5.0: Implementing the IO Ring instead of buffered hashing, huge performance gains. The IO Ring is thread local, uses DMA and direct disk I/O, bypassing the OS cash completely, it supports bashing multiple submissions and can handle multiple files at the same time. +5.0: Implementing the IO Ring for windows and ui_uring for linux instead of buffered hashing, huge performance gains. The IO Ring is event driven, thread local, uses DMA and direct disk I/O, bypassing the OS cash completely, registred buffers, it supports bashing multiple submissions and can handle multiple files at the same time. Hashing small files using XXH3_128bits() instead of the streaming pipeline(XXH3_128bits_reset(), XXH3_128bits_update(), XXH3_128bits_digest()), this reduses the overhead of creating a state and digest, coupled with the IO Ring it improves the hashing of small files whose size is inferior to the size of IO Ring buffers fixing the xxh_x86dispatch warnings Updating the progress printing function diff --git a/compile_commands.json b/compile_commands.json index 34a424d..0b29205 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -4,4 +4,4 @@ "command": "clang-cl /O2 file_hasher.c xxh_x86dispatch.c", "file": "file_hasher.c" } -] \ No newline at end of file +] diff --git a/file_hasher b/file_hasher new file mode 100644 index 0000000000000000000000000000000000000000..8c12d074d16ec49d546cfa63db36d9f337926f66 GIT binary patch literal 245536 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 literal 0 HcmV?d00001 diff --git a/file_hasher.c b/file_hasher.c index 9bbe3d2..7948f12 100644 --- a/file_hasher.c +++ b/file_hasher.c @@ -87,7 +87,7 @@ int main(int argc, char **argv) { printf(" Selected instruction set: %s\n", get_xxhash_instruction_set()); // Align IO Ring block size to the system page size - g_ioring_buffer_size = ALIGN_UP_POW2(IORING_BUFFER_SIZE, g_pagesize); + g_ioring_buffer_size = ALIGN_UP_POW2(g_ioring_buffer_size, g_pagesize); // ------------------------------- // Scanning and hashing // ------------------------------- @@ -253,7 +253,7 @@ int main(int argc, char **argv) { FILE *f = fopen(FILE_HASHES_TXT, "wb"); - for (int i = 0; i < num_threads; i++) { + for (int i = 0; i < num_hash_threads; i++) { mem_arena *arena = workers[i].arena; u8 *arena_base = (u8 *)arena + ALIGN_UP_POW2(sizeof(mem_arena), arena->align); @@ -265,14 +265,13 @@ int main(int argc, char **argv) { // ------------------------------- // Print summary // ------------------------------- - // DEBUG uint64_t incomplete = atomic_load(&g_io_ring_fallbacks); if (incomplete > 0) { - printf( - "\nI/O Ring incomplete files: %llu (fallback to buffered I/O used)\n", - (unsigned long long)incomplete); + printf("\nWARNING: I/O Ring incomplete files: %llu (fallback to buffered " + "I/O used)\n", + (unsigned long long)incomplete); } - // + double total_seconds = timer_elapsed(&total_timer); printf("Completed hashing %zu files\n", total_found); diff --git a/io_uring_test b/io_uring_test new file mode 100644 index 0000000000000000000000000000000000000000..6315aaab05dd61e1fb713218a40b7939f688c2a6 GIT binary patch literal 27808 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 YxmuCFql{!fI-oS@-&=7Wd93lj0qbTVjsO4v literal 0 HcmV?d00001 diff --git a/io_uring_test.c b/io_uring_test.c new file mode 100644 index 0000000..735a44d --- /dev/null +++ b/io_uring_test.c @@ -0,0 +1,454 @@ +/* +# Compile +gcc -o io_uring_test io_uring_test.c -luring + +# Run +./io_uring_test + */ +#include "base.h" +#include +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_FILE "test_io_uring.txt" +#define BUFFER_SIZE 4096 +#define NUM_BUFFERS 4 + +// Colors for output +#define COLOR_GREEN "\033[0;32m" +#define COLOR_RED "\033[0;31m" +#define COLOR_YELLOW "\033[0;33m" +#define COLOR_BLUE "\033[0;34m" +#define COLOR_RESET "\033[0m" + +// Test result tracking +typedef struct { + int passed; + int failed; +} TestResults; + +static void print_success(const char *step) { + printf(COLOR_GREEN "[✓] SUCCESS: %s" COLOR_RESET "\n", step); +} + +static void print_failure(const char *step, const char *error) { + printf(COLOR_RED "[✗] FAILED: %s - %s" COLOR_RESET "\n", step, error); +} + +static void print_info(const char *msg) { + printf(COLOR_BLUE "[i] INFO: %s" COLOR_RESET "\n", msg); +} + +static void print_step(const char *step) { + printf(COLOR_YELLOW "\n>>> Testing: %s" COLOR_RESET "\n", step); +} + +// Create a test file with known content +static int create_test_file(void) { + const char *test_content = + "Hello, io_uring! This is a test file for async I/O operations.\n" + "Line 2: Testing reads with registered buffers.\n" + "Line 3: The quick brown fox jumps over the lazy dog.\n" + "Line 4: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" + "Line 5: 0123456789\n"; + + FILE *f = fopen(TEST_FILE, "w"); + if (!f) { + perror("Failed to create test file"); + return -1; + } + + fprintf(f, "%s", test_content); + fclose(f); + + print_info("Test file created successfully"); + return 0; +} + +// Test 1: Create io_uring instance +static int test_io_uring_create(struct io_uring *ring, TestResults *results) { + print_step("io_uring creation"); + + int ret = io_uring_queue_init(256, ring, 0); + if (ret < 0) { + print_failure("io_uring_queue_init", strerror(-ret)); + results->failed++; + return -1; + } + + print_success("io_uring instance created"); + results->passed++; + return 0; +} + +// Test 2: Register buffers +static int test_register_buffers(struct io_uring *ring, void **buffers, + struct iovec *iovs, TestResults *results) { + print_step("Buffer registration"); + + // Allocate and prepare buffers + size_t total_size = BUFFER_SIZE * NUM_BUFFERS; + *buffers = aligned_alloc(4096, total_size); // Page-aligned for O_DIRECT + if (!*buffers) { + print_failure("Buffer allocation", strerror(errno)); + results->failed++; + return -1; + } + + // Initialize iovecs + for (int i = 0; i < NUM_BUFFERS; i++) { + iovs[i].iov_base = (char *)*buffers + (i * BUFFER_SIZE); + iovs[i].iov_len = BUFFER_SIZE; + memset(iovs[i].iov_base, 0, BUFFER_SIZE); + } + + int ret = io_uring_register_buffers(ring, iovs, NUM_BUFFERS); + if (ret < 0) { + print_failure("io_uring_register_buffers", strerror(-ret)); + results->failed++; + return -1; + } + + print_success("Buffers registered successfully"); + results->passed++; + return 0; +} + +// Test 3: Open file +// Modified test_open_file function +static int test_open_file(int *fd, TestResults *results) { + print_step("File opening"); + + // Get file size + struct stat st; + if (stat(TEST_FILE, &st) != 0) { + print_failure("stat", strerror(errno)); + results->failed++; + return -1; + } + + // Check if file size is page-aligned + int page_size = plat_get_pagesize(); + size_t file_size = st.st_size; + + printf(" File size: %zu bytes\n", file_size); + printf(" Page size: %d bytes\n", page_size); + + if (file_size % page_size != 0) { + printf(" Extending read size from %zu to %zu bytes\n", file_size, + ALIGN_UP_POW2(file_size, page_size)); + } + + // Try O_DIRECT first + *fd = open(TEST_FILE, O_RDONLY | O_DIRECT); + if (*fd < 0) { + print_info("O_DIRECT failed, trying without it"); + *fd = open(TEST_FILE, O_RDONLY); + if (*fd < 0) { + print_failure("open", strerror(errno)); + results->failed++; + return -1; + } + print_info("Using buffered I/O (O_DIRECT not available)"); + } else { + print_success("File opened with O_DIRECT"); + } + + results->passed++; + return 0; +} + +// Test 4: Build and submit read operation +static int test_submit_read(struct io_uring *ring, int fd, struct iovec *iovs, + int buffer_id, uint64_t user_data, + TestResults *results) { + print_step("Building and submitting read operation"); + + // Get file size for proper alignment + struct stat st; + if (fstat(fd, &st) != 0) { + print_failure("fstat", strerror(errno)); + results->failed++; + return -1; + } + + u32 page_size = plat_get_pagesize(); + size_t file_size = st.st_size; + size_t read_size = BUFFER_SIZE; + + // For O_DIRECT, ensure read size is sector-aligned + if (read_size > file_size) { + read_size = ALIGN_UP_POW2(file_size, page_size); + printf(" Adjusted read size to %zu bytes for O_DIRECT alignment\n", + read_size); + } + + struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + if (!sqe) { + print_failure("io_uring_get_sqe", "No available SQE"); + results->failed++; + return -1; + } + + // Prepare read operation using registered buffer + io_uring_prep_read_fixed(sqe, fd, iovs[buffer_id].iov_base, read_size, 0, + buffer_id); + io_uring_sqe_set_data64(sqe, user_data); + + int ret = io_uring_submit(ring); + if (ret < 0) { + print_failure("io_uring_submit", strerror(-ret)); + results->failed++; + return -1; + } + + print_success("Read operation submitted successfully"); + results->passed++; + return 0; +} + +// Test 5: Wait for completion +static int test_wait_completion(struct io_uring *ring, + struct io_uring_cqe **cqe, + TestResults *results) { + print_step("Waiting for completion"); + + int ret = io_uring_wait_cqe(ring, cqe); + if (ret < 0) { + print_failure("io_uring_wait_cqe", strerror(-ret)); + results->failed++; + return -1; + } + + print_success("Completion received"); + results->passed++; + return 0; +} + +// Test 6: Process completion +static int test_process_completion(struct io_uring_cqe *cqe, + uint64_t expected_user_data, + TestResults *results) { + print_step("Processing completion"); + + uint64_t user_data = io_uring_cqe_get_data64(cqe); + int res = cqe->res; + + printf(" Completion data:\n"); + printf(" User data: %lu (expected: %lu)\n", user_data, expected_user_data); + printf(" Result: %d bytes read\n", res); + + if (user_data != expected_user_data) { + print_failure("User data mismatch", + "User data doesn't match expected value"); + results->failed++; + return -1; + } + + if (res < 0) { + print_failure("Read operation", strerror(-res)); + results->failed++; + return -1; + } + + print_success("Completion processed successfully"); + results->passed++; + return res; // Return number of bytes read +} + +// Test 7: Verify read data +static int test_verify_data(struct iovec *iovs, int buffer_id, int bytes_read, + TestResults *results) { + print_step("Data verification"); + + char *data = (char *)iovs[buffer_id].iov_base; + + printf(" Read data (first 200 chars):\n"); + printf(" ---\n"); + for (int i = 0; i < bytes_read && i < 200; i++) { + putchar(data[i]); + } + if (bytes_read > 200) + printf("..."); + printf("\n ---\n"); + + // Check if data is not empty + if (bytes_read == 0) { + print_failure("Data verification", "No data read"); + results->failed++; + return -1; + } + + // Check if data contains expected content + if (strstr(data, "io_uring") == NULL) { + print_failure("Data verification", "Expected content not found"); + results->failed++; + return -1; + } + + print_success("Data verified successfully"); + results->passed++; + return 0; +} + +// Test 8: Test multiple concurrent reads +static int test_concurrent_reads(struct io_uring *ring, int fd, + struct iovec *iovs, TestResults *results) { + print_step("Concurrent reads test"); + + int num_reads = 3; + int submitted = 0; + + // Submit multiple reads + for (int i = 0; i < num_reads; i++) { + struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + if (!sqe) { + print_failure("Getting SQE for concurrent read", "No available SQE"); + results->failed++; + return -1; + } + + off_t offset = i * 100; // Read from different offsets + io_uring_prep_read_fixed(sqe, fd, iovs[i].iov_base, BUFFER_SIZE, offset, i); + io_uring_sqe_set_data64(sqe, i); + submitted++; + } + + int ret = io_uring_submit(ring); + if (ret != submitted) { + char msg[64]; + snprintf(msg, sizeof(msg), "Expected %d, got %d", submitted, ret); + + print_failure("Submitting concurrent reads", msg); + results->failed++; + return -1; + } + + print_success("Concurrent reads submitted"); + + // Wait for and process completions + for (int i = 0; i < submitted; i++) { + struct io_uring_cqe *cqe; + ret = io_uring_wait_cqe(ring, &cqe); + if (ret < 0) { + print_failure("Waiting for concurrent read completion", strerror(-ret)); + results->failed++; + return -1; + } + + uint64_t user_data = io_uring_cqe_get_data64(cqe); + int res = cqe->res; + + printf(" Concurrent read %lu completed: %d bytes read\n", user_data, res); + io_uring_cqe_seen(ring, cqe); + } + + print_success("Concurrent reads completed successfully"); + results->passed++; + return 0; +} + +// Cleanup function +static void cleanup(struct io_uring *ring, int fd, void *buffers) { + if (fd >= 0) + close(fd); + if (buffers) { + io_uring_unregister_buffers(ring); + free(buffers); + } + io_uring_queue_exit(ring); + remove(TEST_FILE); +} + +int main() { + TestResults results = {0, 0}; + struct io_uring ring; + int fd = -1; + void *buffers = NULL; + struct iovec iovs[NUM_BUFFERS]; + + printf(COLOR_BLUE "\n========================================\n"); + printf(" io_uring Test Suite\n"); + printf("========================================\n" COLOR_RESET); + + // Create test file + if (create_test_file() != 0) { + return 1; + } + + // Test 1: Create io_uring + if (test_io_uring_create(&ring, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Test 2: Register buffers + if (test_register_buffers(&ring, &buffers, iovs, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Test 3: Open file + if (test_open_file(&fd, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Test 4: Submit read + uint64_t test_user_data = 12345; + if (test_submit_read(&ring, fd, iovs, 0, test_user_data, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Test 5: Wait for completion + struct io_uring_cqe *cqe; + if (test_wait_completion(&ring, &cqe, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Test 6: Process completion + int bytes_read = test_process_completion(cqe, test_user_data, &results); + if (bytes_read < 0) { + cleanup(&ring, fd, buffers); + return 1; + } + io_uring_cqe_seen(&ring, cqe); + + // Test 7: Verify data + if (test_verify_data(iovs, 0, bytes_read, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Test 8: Concurrent reads + if (test_concurrent_reads(&ring, fd, iovs, &results) != 0) { + cleanup(&ring, fd, buffers); + return 1; + } + + // Print summary + printf(COLOR_BLUE "\n========================================\n"); + printf(" TEST SUMMARY\n"); + printf("========================================\n" COLOR_RESET); + printf(" Total tests: %d\n", results.passed + results.failed); + printf(COLOR_GREEN " Passed: %d\n" COLOR_RESET, results.passed); + if (results.failed > 0) { + printf(COLOR_RED " Failed: %d\n" COLOR_RESET, results.failed); + } else { + printf(COLOR_GREEN " ✓ ALL TESTS PASSED!\n" COLOR_RESET); + } + + // Cleanup + cleanup(&ring, fd, buffers); + + return results.failed > 0 ? 1 : 0; +} diff --git a/platform.c b/platform.c index c9cce03..b277ea6 100644 --- a/platform.c +++ b/platform.c @@ -5,6 +5,7 @@ #include "lf_mpmc.h" #include "arena.c" +#include // xxhash include #define XXH_STATIC_LINKING_ONLY @@ -119,12 +120,14 @@ const char *get_xxhash_instruction_set(void) { #if defined(_WIN32) || defined(_WIN64) typedef HANDLE FileHandle; +#define FLAG_SEQUENTIAL_READ FILE_FLAG_SEQUENTIAL_SCAN +#define FLAG_ASYNC_DIRECT_READ (FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING) #define INVALID_FILE_HANDLE INVALID_HANDLE_VALUE // File open function -static FileHandle os_file_open(const char *path) { +static FileHandle os_file_open(const char *path, DWORD flags) { return CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + NULL, OPEN_EXISTING, flags, NULL); } // File read function @@ -141,11 +144,21 @@ static void os_file_close(FileHandle handle) { CloseHandle(handle); } #elif defined(__linux__) typedef int FileHandle; +#define FLAG_SEQUENTIAL_READ (0) +#define FLAG_ASYNC_DIRECT_READ (O_DIRECT) #define INVALID_FILE_HANDLE (-1) // File open function -static FileHandle os_file_open(const char *path) { - return open(path, O_RDONLY | O_NOFOLLOW); +static FileHandle os_file_open(const char *path, int flags) { + // Combine your mandatory flags with the user-provided flag + int fd = open(path, O_RDONLY | O_NOFOLLOW | flags); + + // If sequential was requested, advise the kernel + if (fd != -1 && (flags == FLAG_SEQUENTIAL_READ)) { + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + } + + return fd; } // File read function @@ -583,7 +596,7 @@ void scan_folder(const char *base, ScannerContext *ctx) { #elif defined(__linux__) static int platform_get_file_times_fd(int dir_fd, const char *name, - time_t *created, time_t *modified) { + uint64_t *created, uint64_t *modified) { struct stat st; if (fstatat(dir_fd, name, &st, 0) == 0) { *created = st.st_ctime; // or st.st_birthtime on systems that support it @@ -613,96 +626,64 @@ static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, return 0; } return -1; +} - void scan_folder(const char *base, ScannerContext *ctx) { - PathBuilder pb; - path_builder_init(&pb, base); +void scan_folder(const char *base, ScannerContext *ctx) { + PathBuilder pb; + path_builder_init(&pb, base); - int dir_fd = open(base, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); - if (dir_fd == -1) - return; + int dir_fd = open(base, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (dir_fd == -1) + return; - DIR *dir = fdopendir(dir_fd); - if (!dir) { - close(dir_fd); - return; - } + DIR *dir = fdopendir(dir_fd); + if (!dir) { + close(dir_fd); + return; + } - struct dirent *entry; + struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.' && - (entry->d_name[1] == 0 || - (entry->d_name[1] == '.' && entry->d_name[2] == 0))) - continue; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.' && + (entry->d_name[1] == 0 || + (entry->d_name[1] == '.' && entry->d_name[2] == 0))) + continue; - size_t name_len = strlen(entry->d_name); - path_builder_set_filename(&pb, entry->d_name, name_len); + size_t name_len = strlen(entry->d_name); + path_builder_set_filename(&pb, entry->d_name, name_len); - int file_type = DT_UNKNOWN; + int file_type = DT_UNKNOWN; #ifdef _DIRENT_HAVE_D_TYPE - file_type = entry->d_type; + file_type = entry->d_type; #endif - // Fast path using d_type - if (file_type != DT_UNKNOWN) { - if (file_type == DT_LNK) - continue; // Skip symlinks + // Fast path using d_type + if (file_type != DT_UNKNOWN) { + if (file_type == DT_LNK) + continue; // Skip symlinks - if (file_type == DT_DIR) { - char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false); - mpmc_push_work(ctx->dir_queue, dir_path); - continue; - } - - if (file_type == DT_REG) { - atomic_fetch_add(&g_files_found, 1); - FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); - - // Use fstatat for file info - struct stat st; - if (fstatat(dir_fd, entry->d_name, &st, 0) == 0) { - // Convert times using fd variant - platform_get_file_times_fd(dir_fd, entry->d_name, &fe->created_time, - &fe->modified_time); - platform_get_file_owner_fd(dir_fd, entry->d_name, fe->owner, - sizeof(fe->owner)); - fe->size_bytes = (uint64_t)st.st_size; - - // Normalize path - char temp_path[MAX_PATHLEN]; - memcpy(temp_path, pb.buffer, - (pb.filename_pos - pb.buffer) + name_len + 1); - normalize_path(temp_path); - - fe->path = - arena_push(&ctx->path_arena, strlen(temp_path) + 1, false); - strcpy(fe->path, temp_path); - - mpmc_push(ctx->file_queue, fe); - } - continue; - } + if (file_type == DT_DIR) { + char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false); + mpmc_push_work(ctx->dir_queue, dir_path); + continue; } - // Fallback for unknown types - struct stat st; - if (fstatat(dir_fd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0) { - if (S_ISLNK(st.st_mode)) - continue; + if (file_type == DT_REG) { + atomic_fetch_add(&g_files_found, 1); + FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); - if (S_ISDIR(st.st_mode)) { - char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false); - mpmc_push_work(ctx->dir_queue, dir_path); - } else if (S_ISREG(st.st_mode)) { - atomic_fetch_add(&g_files_found, 1); - FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); - - platform_get_file_times(pb.buffer, &fe->created_time, - &fe->modified_time); - platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner)); + // Use fstatat for file info + struct stat st; + if (fstatat(dir_fd, entry->d_name, &st, 0) == 0) { + // Convert times using fd variant + platform_get_file_times_fd(dir_fd, entry->d_name, &fe->created_time, + &fe->modified_time); + platform_get_file_owner_fd(dir_fd, entry->d_name, fe->owner, + sizeof(fe->owner)); fe->size_bytes = (uint64_t)st.st_size; + // Normalize path char temp_path[MAX_PATHLEN]; memcpy(temp_path, pb.buffer, (pb.filename_pos - pb.buffer) + name_len + 1); @@ -713,12 +694,44 @@ static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, mpmc_push(ctx->file_queue, fe); } + continue; } } - closedir(dir); // Closes dir_fd automatically + // Fallback for unknown types + struct stat st; + if (fstatat(dir_fd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0) { + if (S_ISLNK(st.st_mode)) + continue; + + if (S_ISDIR(st.st_mode)) { + char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false); + mpmc_push_work(ctx->dir_queue, dir_path); + } else if (S_ISREG(st.st_mode)) { + atomic_fetch_add(&g_files_found, 1); + FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); + + platform_get_file_times(pb.buffer, &fe->created_time, + &fe->modified_time); + platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner)); + fe->size_bytes = (uint64_t)st.st_size; + + char temp_path[MAX_PATHLEN]; + memcpy(temp_path, pb.buffer, + (pb.filename_pos - pb.buffer) + name_len + 1); + normalize_path(temp_path); + + fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false); + strcpy(fe->path, temp_path); + + mpmc_push(ctx->file_queue, fe); + } + } } + closedir(dir); // Closes dir_fd automatically +} + #endif // ------------------------- Scan worker -------------------------------- @@ -745,7 +758,7 @@ static void xxh3_hash_file_stream(const char *path, char *out_hex, XXH3_state_t state; XXH3_128bits_reset(&state); - FileHandle handle = os_file_open(path); + FileHandle handle = os_file_open(path, FLAG_SEQUENTIAL_READ); if (handle == INVALID_FILE_HANDLE) { strcpy(out_hex, "ERROR"); return; @@ -874,23 +887,55 @@ static THREAD_RETURN progress_thread(void *arg) { // ======================== Hash worker IO Ring ======================== // -------------------------- Configuration --------------------------- -#define IORING_BUFFER_SIZE (KiB(512)) -#define NUM_BUFFERS_PER_THREAD 16 -#define MAX_ACTIVE_FILES 8 +// #define IORING_BUFFER_SIZE (KiB(32)) +#define IORING_BUFFER_SIZE (KiB(256)) +#define NUM_BUFFERS_PER_THREAD 32 +#define MAX_ACTIVE_FILES 1 #define SUBMIT_TIMEOUT_MS 30000 -#define USERDATA_REGISTER 1 // Globals u64 g_ioring_buffer_size = 4096 * 64; static atomic_uint_fast64_t g_io_ring_fallbacks = 0; -// -------------------------- File context --------------------------- +// -------------------------- Data structures --------------------------- + +#if defined(_WIN32) || defined(_WIN64) +// Windows I/O Ring types +typedef HIORING AsyncIoRing; +typedef HIORING AsyncIoHandle; +#define INVALID_ASYNC_IO_HANDLE NULL +#define SUBMIT_READ_RETURN_VALUE HRESULT + +#elif defined(__linux__) +// Linux io_uring types +typedef struct { + struct io_uring ring; + int event_fd; + struct io_uring_cqe *cqe_cache; + int cqe_cache_index; + int cqe_cache_count; +} AsyncIoRingImpl; + +typedef AsyncIoRingImpl *AsyncIoRing; +typedef int AsyncIoHandle; +typedef struct iovec IORING_BUFFER_INFO; +#define INVALID_ASYNC_IO_HANDLE (-1) +#define SUBMIT_READ_RETURN_VALUE int + +typedef struct { + int ResultCode; + uint32_t Information; + uintptr_t UserData; +} AsyncIoCompletion; + +#endif + typedef struct IoBuffer IoBuffer; typedef struct FileReadContext { - HANDLE hFile; + FileHandle hFile; uint64_t file_size; - bool use_incremental_hash; + int use_incremental_hash; union { XXH3_state_t hash_state; // For incremental hash (large files) XXH128_hash_t single_hash; // For single-shot hash (small files) @@ -922,7 +967,8 @@ typedef struct IoBuffer { uint64_t offset; size_t size; size_t bytes_read; - HRESULT result; + SUBMIT_READ_RETURN_VALUE result; + int buffer_id; int completed; @@ -933,18 +979,374 @@ typedef struct IoBuffer { // Thread-local I/O Ring context typedef struct ThreadIoContext { - HIORING ring; - HANDLE completion_event; + AsyncIoRing ring; + void *completion_event; + unsigned char *fallback_buffer; IoBuffer buffers[NUM_BUFFERS_PER_THREAD]; int buffer_pool[NUM_BUFFERS_PER_THREAD]; int free_count; + int submitting; int num_submissions; int active_files; - int initialized; + + int use_registered_buffers; } ThreadIoContext; -static _Thread_local ThreadIoContext *g_thread_ctx = NULL; +typedef struct { + uint32_t MaxSubmissionQueueSize; + uint32_t MaxCompletionQueueSize; + uint32_t MaxVersion; +} AsyncIoCapabilities; +// ----------------------------- Async I/O Abstraction ------------------------- +#if defined(_WIN32) || defined(_WIN64) + +// Windows I/O Ring functions +static void async_io_query_capabilities(AsyncIoCapabilities *caps) { + IORING_CAPABILITIES win_caps; + QueryIoRingCapabilities(&win_caps); + caps->MaxSubmissionQueueSize = win_caps.MaxSubmissionQueueSize; + caps->MaxCompletionQueueSize = win_caps.MaxCompletionQueueSize; + caps->MaxVersion = win_caps.MaxVersion; +} + +static void *async_io_create_completion_event(void) { + return CreateEvent(NULL, FALSE, FALSE, NULL); +} + +static void async_io_set_completion_event(AsyncIoRing ring, void *event) { + SetIoRingCompletionEvent(ring, event); +} + +static void async_io_wait_for_completion(ThreadIoContext *ctx) { + if (ctx->num_submissions > 0) { + WaitForSingleObject(ctx->completion_event, SUBMIT_TIMEOUT_MS); + return; + } +} + +static int async_io_create_ring(ThreadIoContext *thread_ctx, + uint32_t queue_size) { + IORING_CREATE_FLAGS flags = {0}; + HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, queue_size, queue_size * 2, + &thread_ctx->ring); + + // Create completion event + thread_ctx->completion_event = async_io_create_completion_event(); + if (thread_ctx->completion_event) { + async_io_set_completion_event(thread_ctx->ring, + thread_ctx->completion_event); + } + return SUCCEEDED(hr) ? 0 : -1; +} + +#define USERDATA_REGISTER 1 + +#define MAKE_BUF_INFO(a, l) \ + (IORING_BUFFER_INFO) { .Address = (a), .Length = (uint32_t)(l) } + +static int async_io_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, + uint32_t timeout_ms, uint32_t *submitted) { + HRESULT hr = SubmitIoRing(thread_ctx->ring, 0, timeout_ms, submitted); + // HRESULT hr = SubmitIoRing(ring, wait_count, timeout_ms, submitted); + + // The wait_count in windows is not implemented yet, so we wait with a + // completion event for a single completion + async_io_wait_for_completion(thread_ctx); + + return SUCCEEDED(hr) ? 0 : -1; +} + +static int async_io_register_buffers(ThreadIoContext *thread_ctx, + uint32_t num_buffers, + IORING_BUFFER_INFO *buf_info) { + + HRESULT hr = BuildIoRingRegisterBuffers( + thread_ctx->ring, NUM_BUFFERS_PER_THREAD, buf_info, USERDATA_REGISTER); + if (FAILED(hr)) { + LPSTR messageBuffer = NULL; + + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, NULL); + + if (size > 0) { + fprintf(stderr, "Error registering buffers: %s (0x%08X)\n", messageBuffer, + (unsigned int)hr); + LocalFree(messageBuffer); // Free the memory allocated by FormatMessage + } else { + fprintf(stderr, "Error registering buffers: Unknown HRESULT (0x%08X)\n", + (unsigned int)hr); + } + } + // Submit registration + async_io_submit(thread_ctx, 0, 0, NULL); + + return hr; +} + +static void async_io_close_event(void *event) { CloseHandle(event); } + +static int async_io_close_ring(ThreadIoContext *thread_ctx) { + + if (thread_ctx->completion_event) + async_io_close_event(thread_ctx->completion_event); + CloseIoRing(thread_ctx->ring); + return 0; +} + +static SUBMIT_READ_RETURN_VALUE +async_io_build_read(ThreadIoContext *thread_ctx, AsyncIoHandle file_handle, + uint32_t buffer_id, size_t size, uint64_t offset, + uintptr_t user_data) { + IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(file_handle); + IORING_BUFFER_REF buffer_ref = + IoRingBufferRefFromIndexAndOffset(buffer_id, 0); + HRESULT hr = + BuildIoRingReadFile(thread_ctx->ring, file_ref, buffer_ref, + (uint32_t)size, offset, user_data, IOSQE_FLAGS_NONE); + return hr; +} + +typedef struct { + HRESULT ResultCode; + uint32_t Information; + uintptr_t UserData; +} AsyncIoCompletion; + +static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { + IORING_CQE win_cqe; + + while (1) { + HRESULT hr = PopIoRingCompletion(ring, &win_cqe); + + if (hr == S_FALSE) + return 0; + + if (FAILED(hr)) + return -1; + + // Unlike linux, in addition of IO operations, Windows IO Ring produces CQEs + // (completion queue entries) when doing operations like register buffer or + // submiting, we filter them here cqe.UserData == USERDATA_REGISTER + // cqe.ResultCode == S_OK (or error) + if (win_cqe.UserData == USERDATA_REGISTER) + continue; + + cqe->ResultCode = win_cqe.ResultCode; + cqe->Information = win_cqe.Information; + cqe->UserData = win_cqe.UserData; + + // Check for error and print warning + if (FAILED(win_cqe.ResultCode)) { + char error_msg[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, win_cqe.ResultCode, 0, error_msg, sizeof(error_msg), + NULL); + fprintf(stderr, + "WARNING: I/O completion error - Code: 0x%lx, Error: %s\n", + win_cqe.ResultCode, error_msg); + } + + return 1; + } +} + +#elif defined(__linux__) +// Linux io_uring functions implementation +static void async_io_query_capabilities(AsyncIoCapabilities *caps) { + // Get system limits for io_uring + long max_entries = sysconf(_SC_IOV_MAX); + if (max_entries <= 0) + max_entries = 4096; + + caps->MaxSubmissionQueueSize = + (uint32_t)(max_entries < 4096 ? max_entries : 4096); + caps->MaxCompletionQueueSize = caps->MaxSubmissionQueueSize * 2; + caps->MaxVersion = 1; +} + +// static int async_io_create_ring(uint32_t queue_size, AsyncIoRing *ring) { +static int async_io_create_ring(ThreadIoContext *thread_ctx, + uint32_t queue_size) { + AsyncIoRingImpl *impl = (AsyncIoRingImpl *)calloc(1, sizeof(AsyncIoRingImpl)); + if (!impl) + return -1; + + // Initialize io_uring + struct io_uring_params params = {0}; + + params.flags = + IORING_SETUP_CQSIZE | + IORING_SETUP_SINGLE_ISSUER | // Thread local io_uring + IORING_SETUP_DEFER_TASKRUN; // Do not send interupts when a CQE is ready, + // send them when a wait function is called, + // and groupe them acording to the nbre or + // entries to wait (reduces syscall overhead) + + params.cq_entries = queue_size * 2; + + int ret = io_uring_queue_init_params(queue_size, &impl->ring, ¶ms); + if (ret < 0) { + // Fallback to without params + printf("WARNING: Creating io_uring with default params\n"); + ret = io_uring_queue_init(queue_size, &impl->ring, 0); + if (ret < 0) { + free(impl); + return -1; + } + } + + impl->cqe_cache = NULL; + impl->cqe_cache_index = 0; + impl->cqe_cache_count = 0; + + thread_ctx->ring = impl; + return 0; +} + +#define MAKE_BUF_INFO(a, l) \ + (IORING_BUFFER_INFO) { .iov_base = (a), .iov_len = (size_t)(l) } + +static int async_io_register_buffers(ThreadIoContext *thread_ctx, + uint32_t num_buffers, + IORING_BUFFER_INFO *buf_info) { + AsyncIoRingImpl *impl = (AsyncIoRingImpl *)thread_ctx->ring; + + int hr = io_uring_register_buffers(&impl->ring, buf_info, num_buffers); + + if (hr < 0) { + fprintf(stderr, "Error registering buffers: %s (code: %d)\n", strerror(-hr), + hr); + fprintf(stderr, "WARNING: Memlock limit too low buffer size! Fallback to " + "unregistred buffers\n"); + } + return hr == 0 ? 0 : -1; +} + +static int async_io_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, + uint32_t timeout_ms, uint32_t *submitted) { + AsyncIoRingImpl *impl = (AsyncIoRingImpl *)thread_ctx->ring; + if (!impl) + return -1; + + int ret; + + if (wait_count > 0) { + ret = io_uring_submit_and_wait(&impl->ring, wait_count); + } else { + ret = io_uring_submit(&impl->ring); + } + + if (ret < 0) { + fprintf(stderr, "submit error: %s\n", strerror(-ret)); + return -1; + } + + if (submitted) + *submitted = (uint32_t)ret; + + return 0; +} + +static int async_io_close_ring(ThreadIoContext *thread_ctx) { + AsyncIoRingImpl *impl = (AsyncIoRingImpl *)thread_ctx->ring; + if (!impl) + return -1; + + if (thread_ctx->use_registered_buffers) { + io_uring_unregister_buffers(&impl->ring); + } + close(impl->event_fd); + io_uring_queue_exit(&impl->ring); + free(impl); + + return 0; +} + +static int async_io_build_read(ThreadIoContext *thread_ctx, + AsyncIoHandle file_handle, uint32_t buffer_id, + size_t size, uint64_t offset, + uintptr_t user_data) { + AsyncIoRing ring = thread_ctx->ring; + AsyncIoRingImpl *impl = (AsyncIoRingImpl *)ring; + if (!impl) + return -1; + + struct io_uring_sqe *sqe = io_uring_get_sqe(&impl->ring); + if (!sqe) { + printf("SQE FULL\n"); + return -1; + } + + ThreadIoContext *ctx = thread_ctx; // or pass it properly TODO : look here + + void *buf = ctx->buffers[buffer_id].data; + + if (thread_ctx->use_registered_buffers) { + io_uring_prep_read_fixed(sqe, file_handle, buf, size, offset, buffer_id); + } else { + io_uring_prep_read(sqe, file_handle, buf, size, offset); + } + + io_uring_sqe_set_data64(sqe, user_data); + return 0; +} + +static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { + AsyncIoRingImpl *impl = (AsyncIoRingImpl *)ring; + + struct io_uring_cqe *cqe_ptr = NULL; + + int ret = io_uring_peek_cqe(&impl->ring, &cqe_ptr); + + if (ret == -EAGAIN) { + // No CQE available + return 0; + } + + if (ret < 0) { + // Error + fprintf(stderr, "io_uring_peek_cqe error: %d (%s)\n", ret, strerror(-ret)); + return -1; + } + + if (!cqe_ptr) { + return 0; + } + + int res = cqe_ptr->res; + + if (res >= 0) { + cqe->ResultCode = 0; + cqe->Information = (uint32_t)res; + } else { + cqe->ResultCode = res; + cqe->Information = 0; + } + + cqe->UserData = (uintptr_t)cqe_ptr->user_data; + + io_uring_cqe_seen(&impl->ring, cqe_ptr); + + // Check for error and print warning + if (res < 0) { + fprintf(stderr, "WARNING: I/O completion error - Code: %d, Error: %s\n", + res, strerror(-res)); + } + + return 1; +} + +#endif + +// OS-agnostic helper macros +#define ASYNC_IO_SUCCEEDED(result) ((result) >= 0) +#define ASYNC_IO_FAILED(result) ((result) < 0) + +// ---------------------- FIFO queue operations --------------------------- typedef struct FileQueue { FileReadContext files[MAX_ACTIVE_FILES]; int head; @@ -952,7 +1354,6 @@ typedef struct FileQueue { int count; } FileQueue; -// ---------------------- FIFO queue operations --------------------------- static FileReadContext *fq_push(FileQueue *fq) { if (fq->count == MAX_ACTIVE_FILES) return NULL; @@ -974,96 +1375,115 @@ static void fq_pop(FileQueue *fq) { fq->count--; } +static void fq_remove_at(FileQueue *fq, int index) { + if (fq->count == 0) + return; + + int remove_idx = (fq->head + index) % MAX_ACTIVE_FILES; + + int last_logical = fq->count - 1; + int last_idx = (fq->head + last_logical) % MAX_ACTIVE_FILES; + + // Swap with last logical element if needed + if (index != last_logical) { + fq->files[remove_idx] = fq->files[last_idx]; + } + + // Just decrease count + fq->count--; + + // Recompute tail properly + fq->tail = (fq->head + fq->count) % MAX_ACTIVE_FILES; +} + // ---------------------- Initialize thread context --------------------------- static ThreadIoContext *io_ring_init_thread(void) { - if (g_thread_ctx && g_thread_ctx->initialized) { - return g_thread_ctx; - } + ThreadIoContext *thread_ctx = + (ThreadIoContext *)calloc(1, sizeof(ThreadIoContext)); + if (!thread_ctx) + return NULL; - if (!g_thread_ctx) { - g_thread_ctx = (ThreadIoContext *)calloc(1, sizeof(ThreadIoContext)); - if (!g_thread_ctx) - return NULL; - } + // Query I/O Ring capabilities + AsyncIoCapabilities caps; + async_io_query_capabilities(&caps); + + uint32_t queue_size = caps.MaxSubmissionQueueSize; + if (queue_size > 4096) + queue_size = 4096; // Cap at 4096 for reasonable memory usage // Create I/O Ring - IORING_CAPABILITIES caps; - QueryIoRingCapabilities(&caps); - - UINT32 queue_size = min(4096, caps.MaxSubmissionQueueSize); - IORING_CREATE_FLAGS flags = {0}; - HRESULT hr = CreateIoRing(caps.MaxVersion, flags, queue_size, queue_size * 2, - &g_thread_ctx->ring); - - // Create completion event - g_thread_ctx->completion_event = CreateEvent(NULL, FALSE, FALSE, NULL); - if (g_thread_ctx->completion_event) { - SetIoRingCompletionEvent(g_thread_ctx->ring, - g_thread_ctx->completion_event); + if (async_io_create_ring(thread_ctx, queue_size) != 0) { + free(thread_ctx); + thread_ctx = NULL; + return NULL; } // Initialize buffer pool + thread_ctx->fallback_buffer = (unsigned char *)malloc(READ_BLOCK); + IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD]; u64 buf_pool_size = g_ioring_buffer_size * NUM_BUFFERS_PER_THREAD; - // Reserve and Commit the entire memory chunk + // Reserve and Commit memory for buffers void *base_ptr = plat_mem_reserve(buf_pool_size); if (base_ptr) { if (!plat_mem_commit(base_ptr, buf_pool_size)) { plat_mem_release(base_ptr, 0); + async_io_close_ring(thread_ctx); + free(thread_ctx); + thread_ctx = NULL; return NULL; } } else { + + async_io_close_ring(thread_ctx); + free(thread_ctx); + thread_ctx = NULL; return NULL; } for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + thread_ctx->buffers[i].data = (u8 *)base_ptr + (i * g_ioring_buffer_size); + thread_ctx->buffer_pool[i] = i; + thread_ctx->buffers[i].buffer_id = i; - g_thread_ctx->buffers[i].data = (u8 *)base_ptr + (i * g_ioring_buffer_size); - - g_thread_ctx->buffer_pool[i] = i; - g_thread_ctx->buffers[i].buffer_id = i; - - buf_info[i].Address = g_thread_ctx->buffers[i].data; - buf_info[i].Length = (ULONG)g_ioring_buffer_size; + buf_info[i] = + MAKE_BUF_INFO(thread_ctx->buffers[i].data, g_ioring_buffer_size); } - g_thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; + thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; - HRESULT hb = BuildIoRingRegisterBuffers( - g_thread_ctx->ring, NUM_BUFFERS_PER_THREAD, buf_info, USERDATA_REGISTER); + // Register buffers + int hr = + async_io_register_buffers(thread_ctx, NUM_BUFFERS_PER_THREAD, buf_info); - if (FAILED(hb)) { - printf("Buffer registration failed: 0x%lx\n", hb); - return NULL; - } + thread_ctx->use_registered_buffers = (hr == 0); + thread_ctx->submitting = 1; + thread_ctx->num_submissions = 0; + thread_ctx->active_files = 0; - // Submit registration - SubmitIoRing(g_thread_ctx->ring, 0, 0, NULL); - - g_thread_ctx->num_submissions = 0; - g_thread_ctx->active_files = 0; - - g_thread_ctx->initialized = 1; - - return g_thread_ctx; + return thread_ctx; } -static void io_ring_cleanup_thread(void) { - if (!g_thread_ctx) +static void io_ring_cleanup_thread(ThreadIoContext *thread_ctx) { + if (!thread_ctx) return; - if (g_thread_ctx->completion_event) - CloseHandle(g_thread_ctx->completion_event); - if (g_thread_ctx->ring) - CloseIoRing(g_thread_ctx->ring); - plat_mem_release(g_thread_ctx->buffers[0].data, 0); - free(g_thread_ctx); - g_thread_ctx = NULL; + if (thread_ctx->ring) + async_io_close_ring(thread_ctx); + + // Free the buffer pool memory + if (thread_ctx->buffers[0].data) { + u64 buf_pool_size = g_ioring_buffer_size * NUM_BUFFERS_PER_THREAD; + plat_mem_release(thread_ctx->buffers[0].data, buf_pool_size); + } + + free(thread_ctx); + thread_ctx = NULL; } -// ---------------------- Buffer get and return ------------------------ +// -------------------------- Buffer get and return ------------------------ static IoBuffer *get_free_buffer(ThreadIoContext *ctx) { if (ctx->free_count == 0) { @@ -1074,7 +1494,7 @@ static IoBuffer *get_free_buffer(ThreadIoContext *ctx) { IoBuffer *buf = &ctx->buffers[idx]; buf->completed = 0; buf->bytes_read = 0; - buf->result = E_PENDING; + buf->result = 0; return buf; } @@ -1087,29 +1507,26 @@ static void return_buffer(ThreadIoContext *ctx, IoBuffer *buf) { } // -------------------------- Submit async read --------------------------- -static HRESULT submit_read(ThreadIoContext *ctx, FileReadContext *file_ctx, - IoBuffer *buf, uint64_t offset, size_t size) { +static int build_read(ThreadIoContext *thread_ctx, FileReadContext *file_ctx, + IoBuffer *buf, uint64_t offset, size_t size) { buf->offset = offset; buf->size = size; buf->file = file_ctx; - IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(file_ctx->hFile); - IORING_BUFFER_REF buffer_ref = - IoRingBufferRefFromIndexAndOffset(buf->buffer_id, 0); + SUBMIT_READ_RETURN_VALUE result = + async_io_build_read(thread_ctx, file_ctx->hFile, buf->buffer_id, size, + offset, (uintptr_t)buf); - HRESULT hr = - BuildIoRingReadFile(ctx->ring, file_ref, buffer_ref, (UINT32)size, offset, - (UINT_PTR)buf, IOSQE_FLAGS_NONE); - - if (SUCCEEDED(hr)) { + if (ASYNC_IO_SUCCEEDED(result)) { file_ctx->active_reads++; file_ctx->reads_submitted++; - ctx->num_submissions++; + thread_ctx->num_submissions++; } else { buf->completed = 1; - return_buffer(ctx, buf); + buf->result = result; // Store the error code + return_buffer(thread_ctx, buf); } - return hr; + return result; } // ------------ Link completed buffers in an ordered list ------------- @@ -1144,26 +1561,24 @@ static void insert_buffer_ordered(FileReadContext *file, IoBuffer *buf) { } // -------------------------- Process completions --------------------------- -static void process_completions(ThreadIoContext *ctx, FileQueue *fq) { - IORING_CQE cqe; +static void process_completions(ThreadIoContext *thread_ctx, FileQueue *fq) { + AsyncIoCompletion cqe; - while (PopIoRingCompletion(ctx->ring, &cqe) == S_OK) { - - if (cqe.UserData == USERDATA_REGISTER || cqe.UserData == 0) - continue; + // Keep processing as long as there are completions available + while (async_io_pop_completion(thread_ctx->ring, &cqe) == 1) { IoBuffer *buf = (IoBuffer *)cqe.UserData; FileReadContext *file = buf->file; buf->result = cqe.ResultCode; - buf->bytes_read = (DWORD)cqe.Information; + buf->bytes_read = cqe.Information; buf->completed = 1; file->active_reads--; file->reads_completed++; - ctx->num_submissions--; + thread_ctx->num_submissions--; - if (SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) { + if (ASYNC_IO_SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) { buf->next = NULL; @@ -1171,7 +1586,7 @@ static void process_completions(ThreadIoContext *ctx, FileQueue *fq) { } else { file->failed_reads++; - return_buffer(ctx, buf); + return_buffer(thread_ctx, buf); } } } @@ -1183,11 +1598,10 @@ static int init_file(FileReadContext *f, FileEntry *fe) { f->fe = fe; f->file_size = fe->size_bytes; - f->hFile = CreateFileA( - fe->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); + // Use the abstracted os_file_open_async for async I/O with no buffering + f->hFile = os_file_open(fe->path, FLAG_ASYNC_DIRECT_READ); - if (f->hFile == INVALID_HANDLE_VALUE) { + if (f->hFile == INVALID_ASYNC_IO_HANDLE) { return 0; } @@ -1201,7 +1615,8 @@ static int init_file(FileReadContext *f, FileEntry *fe) { return 1; } -static void finalize_file(FileReadContext *file, WorkerContext *ctx) { +static void finalize_file(FileReadContext *file, ThreadIoContext *thread_ctx, + WorkerContext *worker_ctx) { FileEntry *fe = file->fe; @@ -1221,8 +1636,9 @@ static void finalize_file(FileReadContext *file, WorkerContext *ctx) { } } else { atomic_fetch_add(&g_io_ring_fallbacks, 1); - xxh3_hash_file_stream(fe->path, hash, NULL); - printf("Fallback for path: %s\n", fe->path); + xxh3_hash_file_stream(fe->path, hash, thread_ctx->fallback_buffer); + // DEBUG + // printf("Fallback for path: %s\n", fe->path); } char created[32], modified[32]; @@ -1231,73 +1647,144 @@ static void finalize_file(FileReadContext *file, WorkerContext *ctx) { double size_kib = (double)fe->size_bytes / 1024.0; - char stack_buf[1024]; + char stack_buf[KiB(4)]; int len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\t%s\t%s\t%s\n", hash, fe->path, size_kib, created, modified, fe->owner); - char *dst = arena_push(&ctx->arena, len, false); + char *dst = arena_push(&worker_ctx->arena, len, false); memcpy(dst, stack_buf, len); atomic_fetch_add(&g_files_hashed, 1); } // -------------------- Hash head file ----------------------- -static void hash_head_file(ThreadIoContext *ctx, FileQueue *fq, - WorkerContext *wctx) { +static void hash_head_file(ThreadIoContext *thread_ctx, FileQueue *fq, + WorkerContext *worker_ctx) { FileReadContext *file = fq_peek(fq); if (!file) return; - while (file->head) { + // Keep hashing while the next buffer in sequence is ready at head + while (file->head && file->head->offset == file->next_hash_offset) { IoBuffer *buf = file->head; - // Check ordering - if (buf->offset != file->bytes_hashed) - return; - - // Consume + // Consume from head file->head = buf->next; if (!file->head) file->tail = NULL; - // Calculate actual bytes to hash (handle last partial sector) - size_t bytes_to_hash = buf->bytes_read; + // Process the buffer + if (ASYNC_IO_SUCCEEDED(buf->result) && buf->bytes_read > 0) { + // Calculate actual bytes to hash (handle last partial sector) + size_t bytes_to_hash = buf->bytes_read; - // If this is the last buffer and we read beyond file size, trim it - if (buf->offset + buf->bytes_read > file->file_size) { - bytes_to_hash = file->file_size - buf->offset; - } - - if (bytes_to_hash > 0) { - if (file->use_incremental_hash) { - // Large file: update incremental hash state - XXH3_128bits_update(&file->hash_state, buf->data, bytes_to_hash); - } else { - // Small file: single-shot hash - file->single_hash = XXH3_128bits(buf->data, bytes_to_hash); + // If this is the last buffer and we read beyond file size, trim it + if (buf->offset + buf->bytes_read > file->file_size) { + bytes_to_hash = file->file_size - buf->offset; } - file->bytes_hashed += bytes_to_hash; - atomic_fetch_add(&g_bytes_processed, bytes_to_hash); + if (bytes_to_hash > 0) { + if (file->use_incremental_hash) { + // Large file: update incremental hash state + XXH3_128bits_update(&file->hash_state, buf->data, bytes_to_hash); + } else { + // Small file: single-shot hash + file->single_hash = XXH3_128bits(buf->data, bytes_to_hash); + } + + file->bytes_hashed += bytes_to_hash; + atomic_fetch_add(&g_bytes_processed, bytes_to_hash); + } + + file->reads_hashed++; + } else if (buf->bytes_read == 0 && ASYNC_IO_SUCCEEDED(buf->result)) { + // Read operation completed with 0 bytes (EOF) + file->reads_hashed++; + } else { + // Read failed + file->failed_reads++; + file->reads_hashed++; } - return_buffer(ctx, buf); + // Move to next offset + file->next_hash_offset += buf->size; + + // Return buffer to pool + return_buffer(thread_ctx, buf); } - // Finalize + // Finalize file when all reads are complete if (file->active_reads == 0 && file->bytes_hashed >= file->file_size) { - finalize_file(file, wctx); - CloseHandle(file->hFile); + finalize_file(file, thread_ctx, worker_ctx); + os_file_close(file->hFile); fq_pop(fq); - ctx->active_files--; + thread_ctx->active_files--; + } +} + +static void hash_ready_files(ThreadIoContext *thread_ctx, FileQueue *fq, + WorkerContext *worker_ctx) { + for (int i = 0; i < fq->count;) { + + int idx = (fq->head + i) % MAX_ACTIVE_FILES; + FileReadContext *file = &fq->files[idx]; + + bool progressed = false; + + // ---- HASH READY BUFFERS ---- + while (file->head) { + + IoBuffer *buf = file->head; + + if (buf->offset != file->bytes_hashed) + break; + + progressed = true; + + file->head = buf->next; + if (!file->head) + file->tail = NULL; + + size_t bytes_to_hash = buf->bytes_read; + + if (buf->offset + buf->bytes_read > file->file_size) { + bytes_to_hash = file->file_size - buf->offset; + } + + if (bytes_to_hash > 0) { + if (file->use_incremental_hash) { + XXH3_128bits_update(&file->hash_state, buf->data, bytes_to_hash); + } else { + file->single_hash = XXH3_128bits(buf->data, bytes_to_hash); + } + + file->bytes_hashed += bytes_to_hash; + atomic_fetch_add(&g_bytes_processed, bytes_to_hash); + } + + return_buffer(thread_ctx, buf); + } + + // ---- FINALIZE ---- + if (file->active_reads == 0 && file->bytes_hashed >= file->file_size) { + + finalize_file(file, thread_ctx, worker_ctx); + os_file_close(file->hFile); + + fq_remove_at(fq, i); + thread_ctx->active_files--; + + continue; + } + i++; } } // ------------- Submit pending reads - fill all free buffers ----------------- -static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, - WorkerContext *worker_ctx, int *submitting) { +static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, + WorkerContext *worker_ctx) { MPMCQueue *file_queue = worker_ctx->file_queue; @@ -1309,7 +1796,7 @@ static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, if (f) { while (f->next_read_offset < f->file_size) { - IoBuffer *buf = get_free_buffer(ctx); + IoBuffer *buf = get_free_buffer(thread_ctx); if (!buf) return; @@ -1318,7 +1805,7 @@ static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, // Check if this is the last read and the file size is not // sector-aligned - BOOL is_last_read = (remaining <= g_ioring_buffer_size); + int is_last_read = (remaining <= g_ioring_buffer_size); if (remaining >= g_ioring_buffer_size) { // Normal full read @@ -1333,10 +1820,11 @@ static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, } } - HRESULT hr = submit_read(ctx, f, buf, f->next_read_offset, size); + SUBMIT_READ_RETURN_VALUE hr = + build_read(thread_ctx, f, buf, f->next_read_offset, size); - if (FAILED(hr)) { - return_buffer(ctx, buf); + if (ASYNC_IO_FAILED(hr)) { + return_buffer(thread_ctx, buf); f->failed_reads++; f->active_reads = 0; f->reads_submitted = 0; @@ -1345,22 +1833,19 @@ static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, } f->next_read_offset += size; - - if (ctx->num_submissions >= NUM_BUFFERS_PER_THREAD) - return; } } // Add new file if possible - if (!*submitting) + if (!thread_ctx->submitting) return; - if (ctx->active_files >= MAX_ACTIVE_FILES) + if (thread_ctx->active_files >= MAX_ACTIVE_FILES) return; FileEntry *fe = mpmc_pop(file_queue); if (!fe) { - *submitting = 0; + thread_ctx->submitting = 0; return; } @@ -1369,37 +1854,24 @@ static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, if (!init_file(newf, fe)) { // File can't be opened with NO_BUFFERING, process with fallback char hash[HASH_STRLEN]; - finalize_file(newf, worker_ctx); + finalize_file(newf, thread_ctx, worker_ctx); fq_pop(fq); continue; } f = newf; - ctx->active_files++; + thread_ctx->active_files++; } } -// -------------------------- Wait for completions --------------------------- -static void wait_for_completions(ThreadIoContext *ctx) { - - // If there are in-flight I/O requests → wait for completion - if (ctx->num_submissions > 0) { - WaitForSingleObject(ctx->completion_event, SUBMIT_TIMEOUT_MS); - return; - } - - // Sleep(1); -} - // -------------------------- Hash worker I/O Ring --------------------------- static THREAD_RETURN hash_worker_io_ring(void *arg) { WorkerContext *ctx = (WorkerContext *)arg; // Init IO ring - ThreadIoContext *ring_ctx = io_ring_init_thread(); - if (!ring_ctx || !ring_ctx->ring) { - printf("Thread %lu: I/O Ring unavailable, using buffered I/O\n", - GetCurrentThreadId()); + ThreadIoContext *thread_ctx = io_ring_init_thread(); + if (!thread_ctx || !thread_ctx->ring) { + printf("I/O Ring unavailable, using buffered I/O\n"); return hash_worker(arg); } @@ -1407,40 +1879,43 @@ static THREAD_RETURN hash_worker_io_ring(void *arg) { FileQueue fq; memset(&fq, 0, sizeof(fq)); - int submitting = 1; + uint32_t submitted = 0; + uint32_t wait_count; // Main pipeline loop for (;;) { - // 1. Submit new reads - submit_pending_reads(ring_ctx, &fq, ctx, &submitting); + // Submit new reads + build_pending_reads(thread_ctx, &fq, ctx); - UINT32 submitted = 0; - SubmitIoRing(ring_ctx->ring, 0, 0, &submitted); + wait_count = MIN(thread_ctx->num_submissions, NUM_BUFFERS_PER_THREAD - 2); - // 5. Avoid busy witing - wait_for_completions(ring_ctx); + submitted = 0; + // async_io_submit(ring_ctx->ring, 0, 0, &submitted); + async_io_submit(thread_ctx, wait_count, 0, &submitted); - // 2. Process completions - process_completions(ring_ctx, &fq); - - // 3. Hash files - for (int i = 0; i < fq.count; i++) { - hash_head_file(ring_ctx, &fq, ctx); - } + // Process completions + process_completions(thread_ctx, &fq); // debug // printf("Free buffers: %d, Submissions: %d, Active files: %d\n", // ring_ctx->free_count, ring_ctx->num_submissions, // ring_ctx->active_files); + // debug end - // 4. Exit condition - if (!submitting && ring_ctx->active_files == 0 && - ring_ctx->num_submissions == 0) { + // Hash files + for (int i = 0; i < fq.count; i++) { + hash_head_file(thread_ctx, &fq, ctx); + } + // hash_ready_files(ring_ctx, &fq, ctx); + + // Exit condition + if (!thread_ctx->submitting && thread_ctx->active_files == 0 && + thread_ctx->num_submissions == 0) { break; } } - io_ring_cleanup_thread(); + io_ring_cleanup_thread(thread_ctx); return THREAD_RETURN_VALUE; }