From d4ba121b56c237cf8bf3d25494ae0f02a16f8ac1 Mon Sep 17 00:00:00 2001 From: amir Date: Tue, 31 Mar 2026 00:26:03 +0100 Subject: [PATCH 1/7] Implementation of IO Ring in Windows Fixing the two compilation warnings. --- .../clangd/index/arena.c.9D15F3F90FD5376F.idx | Bin 0 -> 15052 bytes .../clangd/index/arena.h.D70DBB2C6778A245.idx | Bin 0 -> 8146 bytes .../clangd/index/base.h.2DF61E974E1BB064.idx | Bin 0 -> 5110 bytes .../index/file_hasher.c.AC1BEF31045A7497.idx | Bin 0 -> 3904 bytes .../index/lf_mpmc.h.FB4A8CD7AC664EBA.idx | Bin 0 -> 6734 bytes .../index/platform.c.2B562A3FE6816950.idx | Bin 0 -> 25908 bytes .../xxh_x86dispatch.h.3E8D4826C191778C.idx | Bin 0 -> 2570 bytes .../index/xxhash.h.11DA710B069D4A59.idx | Bin 0 -> 124706 bytes .gitignore | 1 + README.md | 14 +- base.h | 16 +- compile_commands.json | 7 + file_hasher.c | 64 +- io_ring_test.c | 147 ++++ ioringapi.c | 285 +++++++ platform.c | 694 ++++++++++++++---- xxh_x86dispatch.c | 2 + 17 files changed, 1078 insertions(+), 152 deletions(-) create mode 100644 .cache/clangd/index/arena.c.9D15F3F90FD5376F.idx create mode 100644 .cache/clangd/index/arena.h.D70DBB2C6778A245.idx create mode 100644 .cache/clangd/index/base.h.2DF61E974E1BB064.idx create mode 100644 .cache/clangd/index/file_hasher.c.AC1BEF31045A7497.idx create mode 100644 .cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx create mode 100644 .cache/clangd/index/platform.c.2B562A3FE6816950.idx create mode 100644 .cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx create mode 100644 .cache/clangd/index/xxhash.h.11DA710B069D4A59.idx create mode 100644 compile_commands.json create mode 100644 io_ring_test.c create mode 100644 ioringapi.c diff --git a/.cache/clangd/index/arena.c.9D15F3F90FD5376F.idx b/.cache/clangd/index/arena.c.9D15F3F90FD5376F.idx new file mode 100644 index 0000000000000000000000000000000000000000..c010d8446142e3379786c65bd590fb92bf4c8b83 GIT binary patch literal 15052 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 HQI7WivN6hD literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/arena.h.D70DBB2C6778A245.idx b/.cache/clangd/index/arena.h.D70DBB2C6778A245.idx new file mode 100644 index 0000000000000000000000000000000000000000..fc1a62eb3622636b20d615bab03547459e76b4e6 GIT binary patch literal 8146 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 a@qFvc1na|o$UA9C@a5l5S*trHi2nlPs|{cP literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/base.h.2DF61E974E1BB064.idx b/.cache/clangd/index/base.h.2DF61E974E1BB064.idx new file mode 100644 index 0000000000000000000000000000000000000000..cd6107e707535eb8a13671402c2bef755800d0af GIT binary patch literal 5110 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 literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx b/.cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx new file mode 100644 index 0000000000000000000000000000000000000000..26a0ed21c617715979968e7302ae05ebeccf8667 GIT binary patch literal 6734 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 P#j2e7U=mDBAVK&q6FBs} literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/platform.c.2B562A3FE6816950.idx b/.cache/clangd/index/platform.c.2B562A3FE6816950.idx new file mode 100644 index 0000000000000000000000000000000000000000..4a35d146dcaf8411f9578eabe645c640c2f03a0d GIT binary patch literal 25908 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 literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx b/.cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx new file mode 100644 index 0000000000000000000000000000000000000000..d56cdc9288efb9cd36a79b8a200352eb2908bcfb GIT binary patch literal 2570 zcmbtWe`p(J82*x`vzMLCben6h%{;bR))5wyT#oy(b*(Y&vRT`vp{qI?(&W0_%#V`0 z)Vff_Kh!zd#-NC!xc#BEPA2H~hv3lSMuowUB06TrU?_t*{M}L37M$OGx$koAsznVH z?s>lFd!P4xzxVrSIMmW&wF3}Hgoe_R8nXca*1*I~a(xhnMW! z26rfuWD-Op&vXR4>@?U?SMUhh#;2Rxf?XZWfuQCbRiLF5Rq3thW7tL46}&<`n}L}P zX5|FERn83LRmA%QMLwu$Q&MKQp%`#^rT4!nHFKYqBg;gSF(nDDU@?h*;Z=-aPD+vD zu!Sgp24_{|o+?%W3t6cvF4`OsHN-`idI2N+uXUGpT#HLyDdggeiuD*N8Razqbi^Jn z*C(q=G$AVkF*TlC%;pCY+5{}W_=2n^yHE%JQ6MFjEVIlqjyk!lp}zLFev8(Z*cq+U z3R^VZuyTEn?pumVxm-3E&n6_a)Gx*0y_6#GMuNCd*A#4SS{lr>LsN9!uQ1ugg%IeM zQ)r++&j?Bzwl%oS;4u|RMNJCFquP(K7N z?SAFn+aw^dKy->cARe%S^&4P>LocOH_AGRsC&43F;92Y01gcl9vear-ygxbh$87%P zDH2p-0q^8Rlq9kkc6q=2;`Ejo60E@j*2!`u3jiNYg@tc%{gGqGE_a@-C?_eO!&hT8 zkYXH6jnNE>+0Q5?s?X?J($hgJ11i=FBrdTMoZ<6JD&#q-d490Stm z6mN{hm8_j#V$Mmc$A<4 z8X?^P4-ll$;OO+(gSTeMJLwMC+C9XcM|Bn?e|qi4=eMpFG$)vj$qA-|oqDew`t7@h z=~uO!VLEs(%o(PGQT^(Gzz;-=zuU-LZTqHn9x2kiP89U`buvW zx|`^xaqOu=>P*$8E4LvXB0^3~<8gvP&;z3LIVly>K9yWt*`R$GT?STao*lfg_u`uw HOAYu7W+SLD literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/xxhash.h.11DA710B069D4A59.idx b/.cache/clangd/index/xxhash.h.11DA710B069D4A59.idx new file mode 100644 index 0000000000000000000000000000000000000000..349da000a8e8d2003889f7f855edf5d69bd00e87 GIT binary patch literal 124706 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 q6Ic8G*ABmb1wIHD<8 #include #include #include +#include #include #include #include @@ -11,13 +13,25 @@ #include #if defined(_WIN32) || defined(_WIN64) -#define PLATFORM_WINDOWS 1 +// #define PLATFORM_WINDOWS 1 +// #define WIN32_LEAN_AND_MEAN +// #define NTDDI_VERSION NTDDI_WIN11 +// +// #pragma comment(lib, "kernel32.Lib") + #include #include #include +#include +#include #include #include #include +#include + +#if defined(_MSC_VER) +#pragma comment(lib, "advapi32.lib") +#endif #define strdup _strdup #else diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..34a424d --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,7 @@ +[ + { + "directory": "D:/Code/c/filehasher", + "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.c b/file_hasher.c index 8a2b57f..682f7f7 100644 --- a/file_hasher.c +++ b/file_hasher.c @@ -89,12 +89,33 @@ int main(int argc, char **argv) { // ------------------------------- // Scanning and hashing // ------------------------------- + + // test_io_ring(); MPMCQueue dir_queue; mpmc_init(&dir_queue, MiB(1)); MPMCQueue file_queue; mpmc_init(&file_queue, MiB(1)); + // Starting hash threads + // size_t num_hash_threads = num_threads; + // + // WorkerContext workers[num_hash_threads]; + // Thread *hash_threads = + // arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true); + // + // for (size_t i = 0; i < num_hash_threads; ++i) { + // workers[i].arena = arena_create(¶ms); + // workers[i].file_queue = &file_queue; + // + // if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) + // != + // 0) { + // fprintf(stderr, "Failed to create hash thread %zu\n", i); + // exit(1); + // } + // } + // Starting hash threads size_t num_hash_threads = num_threads; @@ -106,13 +127,46 @@ int main(int argc, char **argv) { workers[i].arena = arena_create(¶ms); workers[i].file_queue = &file_queue; - if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) != + if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker_io_ring, &workers[i]) + != 0) { fprintf(stderr, "Failed to create hash thread %zu\n", i); exit(1); } } + // Starting hash threads + // size_t num_hash_threads = num_threads; + // + // WorkerContext workers[num_hash_threads]; + // Thread *hash_threads = + // arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true); + // + // // Check if I/O Ring is available + // bool io_ring_available = false; + // HIORING test_ring = io_ring_init(); + // if (test_ring) { + // io_ring_available = true; + // io_ring_cleanup(test_ring); + // // printf("I/O Ring is available, using high-performance async I/O\n"); + // } else { + // printf("I/O Ring not available, using buffered I/O\n"); + // } + // + // for (size_t i = 0; i < num_hash_threads; ++i) { + // workers[i].arena = arena_create(¶ms); + // workers[i].file_queue = &file_queue; + // + // // Select the appropriate worker function + // ThreadFunc fn = io_ring_available ? (ThreadFunc)hash_worker_io_ring + // : (ThreadFunc)hash_worker; + // + // if (thread_create(&hash_threads[i], fn, &workers[i]) != 0) { + // fprintf(stderr, "Failed to create hash thread %zu\n", i); + // exit(1); + // } + // } + // Starting progress printing thread Thread progress_thread_handle; if (thread_create(&progress_thread_handle, (ThreadFunc)progress_thread, @@ -209,6 +263,14 @@ 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); + } + // double total_seconds = timer_elapsed(&total_timer); printf("Completed hashing %zu files\n", total_found); diff --git a/io_ring_test.c b/io_ring_test.c new file mode 100644 index 0000000..39fe81f --- /dev/null +++ b/io_ring_test.c @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +// #include "ioringapi.c" +#include + +// Initialize I/O Ring +HIORING io_ring_init(void) { + + // if (!io_ring_load_functions()) { + // printf("[I/O Ring] Failed to load functions\n"); + // return NULL; + // } + + IORING_CAPABILITIES caps; + ZeroMemory(&caps, sizeof(caps)); + + HRESULT hr = QueryIoRingCapabilities(&caps); + if (FAILED(hr)) { + printf("[I/O Ring] QueryIoRingCapabilities failed: 0x%08lx\n", hr); + return NULL; + } + + // printf("[I/O Ring] MaxVersion=%d, MaxSubmission=%u, MaxCompletion=%u\n", + // (int)caps.MaxVersion, caps.MaxSubmissionQueueSize, + // caps.MaxCompletionQueueSize); + + if (caps.MaxVersion < IORING_VERSION_1) { + printf("[I/O Ring] Version too old\n"); + return NULL; + } + + IORING_CREATE_FLAGS flags = {0}; + HIORING ring = NULL; + + // hr = CreateIoRing(IORING_VERSION_1, flags, 256, 512, &ring); + hr = CreateIoRing(caps.MaxVersion, flags, 256, 512, &ring); + if (FAILED(hr)) { + printf("[I/O Ring] CreateIoRing failed: 0x%08lx\n", hr); + return NULL; + } + + // printf("[I/O Ring] Created successfully\n"); + + // Check if read operation is supported + + // HRESULT io_ring_support = IsIoRingOpSupported(ring, IORING_OP_READ); + // if (io_ring_support == S_FALSE) { + // printf("[I/O Ring] Not supported, %ld /n", io_ring_support); + // } + + // Get ring info + IORING_INFO info; + ZeroMemory(&info, sizeof(info)); + GetIoRingInfo(ring, &info); + // printf("[I/O Ring] Submission: %u, Completion: %u\n", + // info.SubmissionQueueSize, info.CompletionQueueSize); + + return ring; +} + +void io_ring_cleanup(HIORING ring) { + if (ring) { + CloseIoRing(ring); + // printf("[I/O Ring] Closed\n"); + } +} + +// Read file using I/O Ring +int io_ring_read_file(HIORING ring, HANDLE hFile, void *buffer, DWORD size, + UINT64 offset) { + + IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(hFile); + IORING_BUFFER_REF buf_ref = IoRingBufferRefFromPointer(buffer); + + HRESULT hr = BuildIoRingReadFile(ring, file_ref, buf_ref, size, offset, + (UINT_PTR)buffer, IOSQE_FLAGS_NONE); + + if (FAILED(hr)) + return -1; + + UINT32 submitted = 0; + hr = SubmitIoRing(ring, 1, INFINITE, &submitted); + if (FAILED(hr) || submitted == 0) + return -1; + + for (;;) { + IORING_CQE cqe; + hr = PopIoRingCompletion(ring, &cqe); + + if (FAILED(hr)) + continue; + + if (cqe.UserData != (UINT_PTR)buffer) + continue; + + if (FAILED(cqe.ResultCode)) + return -1; + + return (int)cqe.Information; + } +} + +// Test function +void test_io_ring(void) { + printf("\n=== Testing I/O Ring ===\n"); + + HIORING ring = io_ring_init(); + if (!ring) { + printf("I/O Ring not available\n"); + return; + } + + // Create test file + HANDLE hFile = CreateFileA("test.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + char test_data[] = + "Hello, I/O Ring! This is a test of the Windows I/O Ring API."; + DWORD written; + WriteFile(hFile, test_data, sizeof(test_data), &written, NULL); + CloseHandle(hFile); + } + + // Read using I/O Ring + hFile = CreateFileA("test.txt", GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + char buffer[512] = {0}; + int bytes = io_ring_read_file(ring, hFile, buffer, sizeof(buffer), 0); + if (bytes > 0) { + printf("Read %d bytes: %s\n", bytes, buffer); + } else { + printf("Failed to read file\n"); + } + CloseHandle(hFile); + } else { + printf("Failed to open test file\n"); + } + + // Cleanup + DeleteFileA("test.txt"); + io_ring_cleanup(ring); + + printf("=== Test complete ===\n\n"); +} diff --git a/ioringapi.c b/ioringapi.c new file mode 100644 index 0000000..202905c --- /dev/null +++ b/ioringapi.c @@ -0,0 +1,285 @@ +#pragma once +#include +#include +#include + +// Forward declarations +typedef struct IORING_HANDLE_REF IORING_HANDLE_REF; +typedef struct IORING_BUFFER_REF IORING_BUFFER_REF; +typedef void *HIORING; + +/* --------------------- Types declaration --------------------- */ +typedef enum IORING_CREATE_ADVISORY_FLAGS { + IORING_CREATE_ADVISORY_FLAGS_NONE, + IORING_CREATE_SKIP_BUILDER_PARAM_CHECKS +} IORING_CREATE_ADVISORY_FLAGS; +// Specifies advisory flags for creating an I/O ring with a call to +// CreateIoRing. + +typedef enum IORING_CREATE_REQUIRED_FLAGS { + IORING_CREATE_REQUIRED_FLAGS_NONE +} IORING_CREATE_REQUIRED_FLAGS; +// Specifies required flags for creating an I/O ring with a call to +// CreateIoRing. + +typedef enum IORING_REF_KIND { + IORING_REF_RAW = 0, + IORING_REF_REGISTERED = 1, +} IORING_REF_KIND; +// Specifies the type of an IORING_HANDLE_REF structure. + +typedef enum IORING_SQE_FLAGS { + IOSQE_FLAGS_NONE, + IOSQE_FLAGS_DRAIN_PRECEDING_OPS +} IORING_SQE_FLAGS; +// Specifies kernel behavior options for I/O ring submission queue entries + +// IORING_REGISTERED_BUFFER structure +typedef struct IORING_REGISTERED_BUFFER { + UINT32 Index; + UINT32 Offset; +} IORING_REGISTERED_BUFFER; + +// IORING_HANDLE_REF +struct IORING_HANDLE_REF { + IORING_REF_KIND Kind; + union { + HANDLE Handle; + UINT32 Index; + } HandleUnion; +}; +// Represents a reference to a file handle used in an I/O ring operation + +// IORING_BUFFER_REF +struct IORING_BUFFER_REF { + IORING_REF_KIND Kind; + union { + void *Address; + IORING_REGISTERED_BUFFER IndexAndOffset; + } BufferUnion; +}; + +typedef struct IORING_BUFFER_INFO { + void *Address; + UINT32 Length; +} IORING_BUFFER_INFO; + +// IORING_BUFFER_REF represents a reference to a buffer used in an I/O ring +// operation + +// IORING_VERSION enumeration +typedef enum IORING_VERSION { + IORING_VERSION_INVALID = 0, + IORING_VERSION_1 = 1, + IORING_VERSION_2 = 2, + IORING_VERSION_3 = 3, + IORING_VERSION_4 = 4, +} IORING_VERSION; + +typedef enum IORING_FEATURE_FLAGS { + IORING_FEATURE_FLAGS_NONE = 0, + IORING_FEATURE_UM_EMULATION = 1 +} IORING_FEATURE_FLAGS; + +// IORING_CAPABILITIES structure +typedef struct IORING_CAPABILITIES { + IORING_VERSION MaxVersion; + UINT32 MaxSubmissionQueueSize; + UINT32 MaxCompletionQueueSize; + IORING_FEATURE_FLAGS FeatureFlags; +} IORING_CAPABILITIES; +// Represents the IORING API capabilities. + +// IORING_CQE structure +typedef struct IORING_CQE { + UINT_PTR UserData; + HRESULT ResultCode; + ULONG_PTR Information; +} IORING_CQE; +// Represents a completed I/O ring queue entry. + +// IORING_CREATE_FLAGS structure +typedef struct IORING_CREATE_FLAGS { + IORING_CREATE_REQUIRED_FLAGS Required; + IORING_CREATE_ADVISORY_FLAGS Advisory; +} IORING_CREATE_FLAGS; +// Specifies flags for creating an I/O ring with a call to CreateIoRing. + +// IORING_INFO structure +typedef struct IORING_INFO { + IORING_VERSION IoRingVersion; + IORING_CREATE_FLAGS Flags; + UINT32 SubmissionQueueSize; + UINT32 CompletionQueueSize; +} IORING_INFO; +// Represents the shape and version information for the specified I/O ring + +// IORING_OP_CODE for IsIoRingOpSupported +typedef enum IORING_OP_CODE { + IORING_OP_NOP = 0, + IORING_OP_READ = 1, + IORING_OP_WRITE = 2, + IORING_OP_FLUSH = 3, + IORING_OP_REGISTER_BUFFERS = 4, + IORING_OP_REGISTER_FILES = 5, + IORING_OP_CANCEL = 6, +} IORING_OP_CODE; + +/* --------------------- Dynamic loader --------------------- */ +// Function pointer types +typedef BOOL(WINAPI *IsIoRingOpSupported_t)(HIORING, IORING_OP_CODE); +typedef HRESULT(WINAPI *QueryIoRingCapabilities_t)(IORING_CAPABILITIES *); +typedef HRESULT(WINAPI *GetIoRingInfo_t)(HIORING, IORING_INFO *); +typedef HRESULT(WINAPI *CreateIoRing_t)(IORING_VERSION, IORING_CREATE_FLAGS, + UINT32, UINT32, HIORING *); +typedef HRESULT(WINAPI *CloseIoRing_t)(HIORING); +typedef HRESULT(WINAPI *SubmitIoRing_t)(HIORING, UINT32, UINT32, UINT32 *); +typedef HRESULT(WINAPI *PopIoRingCompletion_t)(HIORING, IORING_CQE *); +typedef HRESULT(WINAPI *SetIoRingCompletionEvent_t)(HIORING, HANDLE); +typedef HRESULT(WINAPI *BuildIoRingCancelRequest_t)(HIORING, IORING_HANDLE_REF, + UINT_PTR, UINT_PTR); +typedef HRESULT(WINAPI *BuildIoRingReadFile_t)(HIORING, IORING_HANDLE_REF, + IORING_BUFFER_REF, UINT32, + UINT64, UINT_PTR, + IORING_SQE_FLAGS); +typedef HRESULT(WINAPI *BuildIoRingRegisterBuffers_t)( + HIORING, UINT32, IORING_BUFFER_INFO const[], UINT_PTR); + +typedef HRESULT(WINAPI *BuildIoRingRegisterFileHandles_t)(HIORING, UINT32, + HANDLE const[], + UINT_PTR); + +// Core: +// Queries the support of the specified operation for the specified I/O ring +static IsIoRingOpSupported_t IsIoRingOpSupported = NULL; + +// Queries the OS for the supported capabilities for IORINGs +static QueryIoRingCapabilities_t QueryIoRingCapabilities = NULL; + +// Gets information about the API version and queue sizes of an I/O ring +static GetIoRingInfo_t GetIoRingInfo = NULL; + +// Creates a new instance of an I/O ring submission/completion queue pair and +// returns a handle for referencing the I/O ring +static CreateIoRing_t CreateIoRing = NULL; + +// Closes an HIORING handle that was previously opened with a call to +// CreateIoRing +static CloseIoRing_t CloseIoRing = NULL; + +// Submission / completion: +// Submits all constructed but not yet submitted entries to the kernel’s queue +// and optionally waits for a set of operations to complete +static SubmitIoRing_t SubmitIoRing = NULL; + +// Pops a single entry from the completion queue, if one is available +static PopIoRingCompletion_t PopIoRingCompletion = NULL; + +// Registers a completion queue event with an IORING +static SetIoRingCompletionEvent_t SetIoRingCompletionEvent = NULL; + +// Operations: +// Performs an asynchronous read from a file using an I/O ring +static BuildIoRingReadFile_t BuildIoRingReadFile = NULL; + +// Attempts to cancel a previously submitted I/O ring operation +static BuildIoRingCancelRequest_t BuildIoRingCancelRequest = NULL; + +// Registers an array of buffers with the system for future I/O ring operations +static BuildIoRingRegisterBuffers_t BuildIoRingRegisterBuffers = NULL; + +// Registers an array of file handles with the system for future I/O ring +// operations +static BuildIoRingRegisterFileHandles_t BuildIoRingRegisterFileHandles = NULL; + +static int io_ring_loaded = 0; + +static int io_ring_load_functions(void) { + if (io_ring_loaded) + return 1; + + HMODULE hKernel = GetModuleHandleW(L"kernel32.dll"); + if (!hKernel) + return 0; + + IsIoRingOpSupported = + (IsIoRingOpSupported_t)GetProcAddress(hKernel, "IsIoRingOpSupported"); + QueryIoRingCapabilities = (QueryIoRingCapabilities_t)GetProcAddress( + hKernel, "QueryIoRingCapabilities"); + GetIoRingInfo = (GetIoRingInfo_t)GetProcAddress(hKernel, "GetIoRingInfo"); + CreateIoRing = (CreateIoRing_t)GetProcAddress(hKernel, "CreateIoRing"); + CloseIoRing = (CloseIoRing_t)GetProcAddress(hKernel, "CloseIoRing"); + SubmitIoRing = (SubmitIoRing_t)GetProcAddress(hKernel, "SubmitIoRing"); + PopIoRingCompletion = + (PopIoRingCompletion_t)GetProcAddress(hKernel, "PopIoRingCompletion"); + SetIoRingCompletionEvent = (SetIoRingCompletionEvent_t)GetProcAddress( + hKernel, "SetIoRingCompletionEvent"); + BuildIoRingReadFile = + (BuildIoRingReadFile_t)GetProcAddress(hKernel, "BuildIoRingReadFile"); + BuildIoRingCancelRequest = (BuildIoRingCancelRequest_t)GetProcAddress( + hKernel, "BuildIoRingCancelRequest"); + BuildIoRingRegisterBuffers = (BuildIoRingRegisterBuffers_t)GetProcAddress( + hKernel, "BuildIoRingRegisterBuffers"); + BuildIoRingRegisterFileHandles = + (BuildIoRingRegisterFileHandles_t)GetProcAddress( + hKernel, "BuildIoRingRegisterFileHandles"); + + io_ring_loaded = + (IsIoRingOpSupported && QueryIoRingCapabilities && CreateIoRing && + CloseIoRing && SubmitIoRing && PopIoRingCompletion && + SetIoRingCompletionEvent && BuildIoRingReadFile && + BuildIoRingCancelRequest && BuildIoRingRegisterBuffers && + BuildIoRingRegisterFileHandles); + + if (io_ring_loaded) + printf("[I/O Ring] Functions loaded\n"); + else + printf("[I/O Ring] Some functions not available\n"); + + return io_ring_loaded; +} + +/* ------------- Standard helper functions definition ------------- */ +// Creates an instance of the IORING_BUFFER_REF structure with the provided +// buffer index and offset +static inline IORING_BUFFER_REF +IoRingBufferRefFromIndexAndOffset(UINT32 index, UINT32 offset) { + IORING_BUFFER_REF ref; + ref.Kind = IORING_REF_REGISTERED; + ref.BufferUnion.IndexAndOffset.Index = index; + ref.BufferUnion.IndexAndOffset.Offset = offset; + return ref; +} + +// Creates an instance of the IORING_BUFFER_REF structure from the provided +// pointer +static IORING_BUFFER_REF IoRingBufferRefFromPointer(void *addr) { + IORING_BUFFER_REF ref; + ref.Kind = IORING_REF_RAW; + ref.BufferUnion.Address = addr; + return ref; +} + +// Creates an instance of the IORING_HANDLE_REF structure from the provided file +// handle +static IORING_HANDLE_REF IoRingHandleRefFromHandle(HANDLE h) { + IORING_HANDLE_REF ref; + ref.Kind = IORING_REF_RAW; + ref.HandleUnion.Handle = h; + return ref; +} + +// Creates an instance of the IORING_HANDLE_REF structure from the provided +// index +static inline IORING_HANDLE_REF IoRingHandleRefFromIndex(UINT32 index) { + IORING_HANDLE_REF ref; + ref.Kind = IORING_REF_REGISTERED; // MUST be registered + ref.HandleUnion.Index = index; + return ref; +} + +// NOTE: If you are using index-based buffers or handles, make sure you have +// successfully called BuildIoRingRegisterBuffers or +// BuildIoRingRegisterFileHandles first so the kernel has a valid table to look +// into, otherwise the kernel will treat the index as an invalid memory +// address/handle. diff --git a/platform.c b/platform.c index 162e985..dd92946 100644 --- a/platform.c +++ b/platform.c @@ -1,7 +1,5 @@ #pragma once // ensure that a given header file is included only once in a // single compilation unit -#define _CRT_SECURE_NO_WARNINGS - #include "arena.h" #include "base.h" #include "lf_mpmc.h" @@ -9,7 +7,7 @@ #include "arena.c" // xxhash include -#define XXH_INLINE_ALL +#define XXH_STATIC_LINKING_ONLY #include "xxh_x86dispatch.h" // ----------------------------- Config ------------------------------------- @@ -584,8 +582,6 @@ void scan_folder(const char *base, ScannerContext *ctx) { } #elif defined(__linux__) -To test -Choice 1 static int platform_get_file_times_fd(int dir_fd, const char *name, time_t *created, time_t *modified) { struct stat st; @@ -604,9 +600,9 @@ static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, struct passwd pw; struct passwd *result; char buffer[4096]; // Sufficiently large buffer for passwd data - + // Reentrant version (thread-safe) - if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 && + if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 && result != NULL && result->pw_name != NULL) { strncpy(owner, result->pw_name, owner_size - 1); owner[owner_size - 1] = '\0'; @@ -618,170 +614,111 @@ static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, } 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_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_REG) { - atomic_fetch_add(&g_files_found, 1); - FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), - true); + // 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; - // 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)); + 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; - // 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); + fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false); + strcpy(fe->path, temp_path); mpmc_push(ctx->file_queue, fe); } - 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 (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 } - closedir(dir); // Closes dir_fd automatically -} - -// Choice 2 - -// void scan_folder(const char *base, ScannerContext *ctx) { -// PathBuilder pb; -// path_builder_init(&pb, base); -// -// DIR *dir = opendir(base); -// if (!dir) -// return; -// -// struct dirent *entry; -// struct stat st; -// -// 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); -// -// if (lstat(pb.buffer, &st) == 0 && S_ISLNK(st.st_mode)) -// continue; -// -// if (stat(pb.buffer, &st) == 0) { -// 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 { -// atomic_fetch_add(&g_files_found, 1); -// -// FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); -// -// // Create a temporary copy for normalization -// 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); -// -// 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; -// -// mpmc_push(ctx->file_queue, fe); -// } -// } -// } -// -// closedir(dir); -// } - #endif // ------------------------- Scan worker -------------------------------- @@ -864,7 +801,7 @@ static THREAD_RETURN hash_worker(void *arg) { return THREAD_RETURN_VALUE; } -// ----------------------------- Progress display --------------------------- +// ------------------------- Progress display --------------------------- static THREAD_RETURN progress_thread(void *arg) { (void)arg; // Unused parameter @@ -940,3 +877,472 @@ static THREAD_RETURN progress_thread(void *arg) { return THREAD_RETURN_VALUE; } + +// ======================== Hash worker IO Ring ======================== +// -------------------------- Configuration --------------------------- +#define IORING_READ_BLOCK (4096 * 64) +#define NUM_BUFFERS_PER_THREAD 16 +#define SUBMIT_TIMEOUT_MS 30000 +#define USERDATA_REGISTER 1 + +// Global stats +static atomic_uint_fast64_t g_io_ring_fallbacks = 0; + +// -------------------------- Buffer structure --------------------------- +typedef struct IoBuffer { + void *data; + uint64_t offset; + size_t size; + size_t bytes_read; + HRESULT result; + int buffer_id; + int completed; +} IoBuffer; + +// ============================================================================ +// Thread-local I/O Ring context +// ============================================================================ +typedef struct ThreadIoContext { + HIORING ring; + HANDLE completion_event; + IoBuffer buffers[NUM_BUFFERS_PER_THREAD]; + int buffer_pool[NUM_BUFFERS_PER_THREAD]; + int free_count; + int initialized; +} ThreadIoContext; + +// -------------------------- File context --------------------------- +typedef struct FileReadContext { + HANDLE hFile; + uint64_t file_size; + XXH3_state_t hash_state; + char path[MAX_PATH]; + + // Completion tracking + int reads_submitted; + int reads_completed; + int reads_hashed; + uint64_t bytes_hashed; + int failed_reads; + int active_reads; + int buffers_ready; // Count of buffers ready to hash + + // For in-order hashing + uint64_t next_hash_offset; // Next offset that needs to be hashed + uint64_t next_read_offset; // Next offset to submit for reading + +} FileReadContext; + +static _Thread_local ThreadIoContext *g_thread_ctx = NULL; + +// ---------------------- Initialize thread context --------------------------- +static ThreadIoContext *io_ring_init_thread(void) { + if (g_thread_ctx && g_thread_ctx->initialized) { + return g_thread_ctx; + } + + if (!g_thread_ctx) { + g_thread_ctx = (ThreadIoContext *)calloc(1, sizeof(ThreadIoContext)); + if (!g_thread_ctx) + return NULL; + } + + // 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); + } + + // Initialize buffer pool + for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + + // 4096 alignment + void *ptr = _aligned_malloc(IORING_READ_BLOCK, 4096); + if (!ptr) { + return NULL; + } + + g_thread_ctx->buffers[i].data = ptr; + + g_thread_ctx->buffer_pool[i] = i; + g_thread_ctx->buffers[i].buffer_id = i; + } + g_thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; + + IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD]; + + for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + buf_info[i].Address = g_thread_ctx->buffers[i].data; + buf_info[i].Length = IORING_READ_BLOCK; + } + + HRESULT hb = BuildIoRingRegisterBuffers( + g_thread_ctx->ring, NUM_BUFFERS_PER_THREAD, buf_info, USERDATA_REGISTER); + + if (FAILED(hb)) { + printf("Buffer registration failed: 0x%lx\n", hb); + return NULL; + } + + // Submit registration + SubmitIoRing(g_thread_ctx->ring, 0, 0, NULL); + + g_thread_ctx->initialized = 1; + + return g_thread_ctx; +} + +static void io_ring_cleanup_thread(void) { + if (!g_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); + for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + _aligned_free(g_thread_ctx->buffers[i].data); + } + free(g_thread_ctx); + g_thread_ctx = NULL; +} + +// ---------------------- Get a free buffer from pool ------------------------ +static IoBuffer *get_free_buffer(ThreadIoContext *ctx) { + + if (ctx->free_count == 0) { + return NULL; + } + + int idx = ctx->buffer_pool[--ctx->free_count]; + IoBuffer *buf = &ctx->buffers[idx]; + buf->completed = 0; + buf->bytes_read = 0; + buf->result = E_PENDING; + + return buf; +} + +static void return_buffer(ThreadIoContext *ctx, IoBuffer *buf) { + if (!buf) + return; + + ctx->buffer_pool[ctx->free_count++] = buf->buffer_id; +} + +// -------------------------- Submit async read --------------------------- +static HRESULT submit_read(ThreadIoContext *ctx, FileReadContext *file_ctx, + IoBuffer *buf, uint64_t offset, size_t size) { + buf->offset = offset; + buf->size = size; + + IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(file_ctx->hFile); + IORING_BUFFER_REF buffer_ref = + IoRingBufferRefFromIndexAndOffset(buf->buffer_id, 0); + + HRESULT hr = + BuildIoRingReadFile(ctx->ring, file_ref, buffer_ref, (UINT32)size, offset, + (UINT_PTR)buf, IOSQE_FLAGS_NONE); + + if (SUCCEEDED(hr)) { + file_ctx->active_reads++; + file_ctx->reads_submitted++; + } else { + buf->completed = 1; + return_buffer(ctx, buf); + } + return hr; +} + +// -------------------------- Process completions --------------------------- +static int process_completions(ThreadIoContext *ctx, + FileReadContext *file_ctx) { + IORING_CQE cqe; + int processed = 0; + + while (PopIoRingCompletion(ctx->ring, &cqe) == S_OK) { + + if (cqe.UserData == USERDATA_REGISTER || cqe.UserData == 0) + continue; + + IoBuffer *buf = (IoBuffer *)cqe.UserData; + + if (buf && !buf->completed) { + buf->result = cqe.ResultCode; + buf->bytes_read = (DWORD)cqe.Information; + buf->completed = 1; + + if (SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) { + file_ctx->active_reads--; + file_ctx->reads_completed++; + file_ctx->buffers_ready++; + } else { + file_ctx->failed_reads++; + file_ctx->active_reads--; + file_ctx->reads_completed++; + } + + processed++; + } + } + + return processed; +} + +// -------------------- Hash buffer in sequential order ----------------------- +static int hash_sequential_buffers(ThreadIoContext *ctx, + FileReadContext *file_ctx) { + int hashed = 0; + + // Keep hashing while the next buffer in sequence is ready + while (file_ctx->next_hash_offset < file_ctx->file_size) { + // Find the buffer that contains next_hash_offset + IoBuffer *found_buf = NULL; + + for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + IoBuffer *buf = &ctx->buffers[i]; + if (buf->completed && buf->offset == file_ctx->next_hash_offset) { + found_buf = buf; + break; + } + } + + if (!found_buf) + break; // Buffer not ready yet + + // Found the correct buffer in order - hash it + if (SUCCEEDED(found_buf->result) && found_buf->bytes_read > 0) { + XXH3_128bits_update(&file_ctx->hash_state, found_buf->data, + found_buf->bytes_read); + atomic_fetch_add(&g_bytes_processed, found_buf->bytes_read); + + // Update bytes_hashed for this file! + file_ctx->bytes_hashed += found_buf->bytes_read; + file_ctx->reads_hashed++; + file_ctx->buffers_ready--; + + // Mark as processed and return buffer to pool + found_buf->completed = 0; + return_buffer(ctx, found_buf); + + // Move to next offset + file_ctx->next_hash_offset += found_buf->size; + hashed++; + } else if (found_buf->bytes_read == 0 && SUCCEEDED(found_buf->result)) { + // Read operation failed with an error code + file_ctx->reads_hashed++; + file_ctx->buffers_ready--; + found_buf->completed = 0; + return_buffer(ctx, found_buf); + file_ctx->next_hash_offset += found_buf->size; + hashed++; + } else { + // Read failed + file_ctx->failed_reads++; + file_ctx->reads_hashed++; + file_ctx->buffers_ready--; + found_buf->completed = 0; + return_buffer(ctx, found_buf); + file_ctx->next_hash_offset += found_buf->size; + hashed++; + } + } + + return hashed; +} + +// ------------- Submit pending reads - fill all free buffers ----------------- +static int submit_pending_reads(ThreadIoContext *ctx, + FileReadContext *file_ctx) { + int submitted = 0; + + while (1) { + // Check if we have more data to read + uint64_t current_offset = file_ctx->next_read_offset; + int has_data = (current_offset < file_ctx->file_size); + + if (!has_data) + break; + + // Get a free buffer + IoBuffer *buf = get_free_buffer(ctx); + if (!buf) + break; + + size_t remaining = file_ctx->file_size - current_offset; + + size_t bytes_to_read; + + if (remaining >= IORING_READ_BLOCK) { + bytes_to_read = IORING_READ_BLOCK; + } else { + // Round UP to sector size (4096) + bytes_to_read = (remaining + 4095) & ~4095; + } + + HRESULT hr = submit_read(ctx, file_ctx, buf, current_offset, bytes_to_read); + + if (SUCCEEDED(hr)) { + submitted++; + + file_ctx->next_read_offset += bytes_to_read; + } else { + return_buffer(ctx, buf); + break; + } + } + + return submitted; +} + +// -------------------------- Wait for completions --------------------------- +static void wait_for_completions(ThreadIoContext *ctx, + FileReadContext *file_ctx) { + int has_active = (file_ctx->active_reads > 0); + + if (has_active && ctx->completion_event) { + WaitForSingleObject(ctx->completion_event, SUBMIT_TIMEOUT_MS); + } +} + +// ---------------------- Main parallel hashing function ---------------------- +static void xxh3_hash_file_parallel(ThreadIoContext *ctx, const char *path, + char *out_hex, unsigned char *temp_buffer) { + + // Validate I/O Ring + if (!ctx || !ctx->ring) { + xxh3_hash_file_stream(path, out_hex, temp_buffer); + return; + } + + HANDLE hFile = CreateFileA( + path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { + xxh3_hash_file_stream(path, out_hex, temp_buffer); + return; + } + + LARGE_INTEGER file_size; + if (!GetFileSizeEx(hFile, &file_size)) { + xxh3_hash_file_stream(path, out_hex, temp_buffer); + CloseHandle(hFile); + return; + } + + FileReadContext file_ctx; + memset(&file_ctx, 0, sizeof(file_ctx)); + file_ctx.hFile = hFile; + file_ctx.file_size = file_size.QuadPart; + file_ctx.next_hash_offset = 0; + file_ctx.next_read_offset = 0; + strncpy(file_ctx.path, path, MAX_PATH - 1); + file_ctx.path[MAX_PATH - 1] = 0; + XXH3_128bits_reset(&file_ctx.hash_state); + + // Submit initial reads + submit_pending_reads(ctx, &file_ctx); + + if (file_ctx.reads_submitted > 0) { + UINT32 submitted = 0; + SubmitIoRing(ctx->ring, 0, 0, &submitted); + } + + // Main loop + while (file_ctx.reads_hashed < file_ctx.reads_submitted) { + // Process completions + process_completions(ctx, &file_ctx); + + // Hash buffers in sequential order (critical!) + hash_sequential_buffers(ctx, &file_ctx); + + // Submit more reads if needed + if (file_ctx.active_reads < NUM_BUFFERS_PER_THREAD && + file_ctx.next_read_offset < file_ctx.file_size) { + int new_reads = submit_pending_reads(ctx, &file_ctx); + if (new_reads > 0) { + UINT32 submitted = 0; + SubmitIoRing(ctx->ring, 0, 0, &submitted); + } + } + + // Wait if nothing to hash and active reads exist + if (file_ctx.active_reads > 0 && file_ctx.buffers_ready == 0) { + wait_for_completions(ctx, &file_ctx); + } + } + + // Final verification + if (file_ctx.bytes_hashed == file_ctx.file_size && + file_ctx.failed_reads == 0) { + XXH128_hash_t h = XXH3_128bits_digest(&file_ctx.hash_state); + snprintf(out_hex, HASH_STRLEN, "%016llx%016llx", + (unsigned long long)h.high64, (unsigned long long)h.low64); + } else { + if (file_ctx.bytes_hashed != file_ctx.file_size) { + atomic_fetch_add(&g_io_ring_fallbacks, 1); + } + xxh3_hash_file_stream(path, out_hex, temp_buffer); + } + + CloseHandle(hFile); +} + +// -------------------------- Hash worker I/O Ring --------------------------- +static THREAD_RETURN hash_worker_io_ring(void *arg) { + WorkerContext *ctx = (WorkerContext *)arg; + unsigned char *temp_buffer = (unsigned char *)malloc(IORING_READ_BLOCK); + char hash[HASH_STRLEN]; + + if (!temp_buffer) + return THREAD_RETURN_VALUE; + + // Initialize I/O Ring for this thread + 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()); + free(temp_buffer); + return hash_worker(arg); + } + + for (;;) { + FileEntry *fe = mpmc_pop(ctx->file_queue); + if (!fe) + break; + + // Pass the I/O Ring context to the hashing function + xxh3_hash_file_parallel(ring_ctx, fe->path, hash, temp_buffer); + + char created[32], modified[32]; + format_time(fe->created_time, created, sizeof(created)); + format_time(fe->modified_time, modified, sizeof(modified)); + + double size_kib = (double)fe->size_bytes / 1024.0; + + char stack_buf[1024]; + 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); + memcpy(dst, stack_buf, len); + atomic_fetch_add(&g_files_hashed, 1); + } + + io_ring_cleanup_thread(); + free(temp_buffer); + + return THREAD_RETURN_VALUE; +} diff --git a/xxh_x86dispatch.c b/xxh_x86dispatch.c index 0c15820..ddb71e1 100644 --- a/xxh_x86dispatch.c +++ b/xxh_x86dispatch.c @@ -1,3 +1,5 @@ +#define XXH_INLINE_ALL + /* * xxHash - Extremely Fast Hash algorithm * Copyright (C) 2020-2021 Yann Collet From 41ac164881c5e9066fc70861ec107cd35679f87e Mon Sep 17 00:00:00 2001 From: amir Date: Tue, 31 Mar 2026 19:33:39 +0100 Subject: [PATCH 2/7] Updating the IO Ring, Updating the progress printing fn --- .../clangd/index/arena.c.9D15F3F90FD5376F.idx | Bin 15052 -> 0 bytes .../clangd/index/arena.h.D70DBB2C6778A245.idx | Bin 8146 -> 0 bytes .../clangd/index/base.h.2DF61E974E1BB064.idx | Bin 5110 -> 0 bytes .../index/file_hasher.c.AC1BEF31045A7497.idx | Bin 3904 -> 0 bytes .../index/lf_mpmc.h.FB4A8CD7AC664EBA.idx | Bin 6734 -> 0 bytes .../index/platform.c.2B562A3FE6816950.idx | Bin 25908 -> 0 bytes .../xxh_x86dispatch.h.3E8D4826C191778C.idx | Bin 2570 -> 0 bytes .../index/xxhash.h.11DA710B069D4A59.idx | Bin 124706 -> 0 bytes .gitignore | 1 + binaries/changelog.txt | 4 + file_hasher.c | 9 +- platform.c | 94 +++++++++--------- xxh_x86dispatch.c | 2 - 13 files changed, 56 insertions(+), 54 deletions(-) delete mode 100644 .cache/clangd/index/arena.c.9D15F3F90FD5376F.idx delete mode 100644 .cache/clangd/index/arena.h.D70DBB2C6778A245.idx delete mode 100644 .cache/clangd/index/base.h.2DF61E974E1BB064.idx delete mode 100644 .cache/clangd/index/file_hasher.c.AC1BEF31045A7497.idx delete mode 100644 .cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx delete mode 100644 .cache/clangd/index/platform.c.2B562A3FE6816950.idx delete mode 100644 .cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx delete mode 100644 .cache/clangd/index/xxhash.h.11DA710B069D4A59.idx diff --git a/.cache/clangd/index/arena.c.9D15F3F90FD5376F.idx b/.cache/clangd/index/arena.c.9D15F3F90FD5376F.idx deleted file mode 100644 index c010d8446142e3379786c65bd590fb92bf4c8b83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15052 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 HQI7WivN6hD diff --git a/.cache/clangd/index/arena.h.D70DBB2C6778A245.idx b/.cache/clangd/index/arena.h.D70DBB2C6778A245.idx deleted file mode 100644 index fc1a62eb3622636b20d615bab03547459e76b4e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8146 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 a@qFvc1na|o$UA9C@a5l5S*trHi2nlPs|{cP diff --git a/.cache/clangd/index/base.h.2DF61E974E1BB064.idx b/.cache/clangd/index/base.h.2DF61E974E1BB064.idx deleted file mode 100644 index cd6107e707535eb8a13671402c2bef755800d0af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5110 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 diff --git a/.cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx b/.cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx deleted file mode 100644 index 26a0ed21c617715979968e7302ae05ebeccf8667..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6734 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 P#j2e7U=mDBAVK&q6FBs} diff --git a/.cache/clangd/index/platform.c.2B562A3FE6816950.idx b/.cache/clangd/index/platform.c.2B562A3FE6816950.idx deleted file mode 100644 index 4a35d146dcaf8411f9578eabe645c640c2f03a0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25908 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 diff --git a/.cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx b/.cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx deleted file mode 100644 index d56cdc9288efb9cd36a79b8a200352eb2908bcfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2570 zcmbtWe`p(J82*x`vzMLCben6h%{;bR))5wyT#oy(b*(Y&vRT`vp{qI?(&W0_%#V`0 z)Vff_Kh!zd#-NC!xc#BEPA2H~hv3lSMuowUB06TrU?_t*{M}L37M$OGx$koAsznVH z?s>lFd!P4xzxVrSIMmW&wF3}Hgoe_R8nXca*1*I~a(xhnMW! z26rfuWD-Op&vXR4>@?U?SMUhh#;2Rxf?XZWfuQCbRiLF5Rq3thW7tL46}&<`n}L}P zX5|FERn83LRmA%QMLwu$Q&MKQp%`#^rT4!nHFKYqBg;gSF(nDDU@?h*;Z=-aPD+vD zu!Sgp24_{|o+?%W3t6cvF4`OsHN-`idI2N+uXUGpT#HLyDdggeiuD*N8Razqbi^Jn z*C(q=G$AVkF*TlC%;pCY+5{}W_=2n^yHE%JQ6MFjEVIlqjyk!lp}zLFev8(Z*cq+U z3R^VZuyTEn?pumVxm-3E&n6_a)Gx*0y_6#GMuNCd*A#4SS{lr>LsN9!uQ1ugg%IeM zQ)r++&j?Bzwl%oS;4u|RMNJCFquP(K7N z?SAFn+aw^dKy->cARe%S^&4P>LocOH_AGRsC&43F;92Y01gcl9vear-ygxbh$87%P zDH2p-0q^8Rlq9kkc6q=2;`Ejo60E@j*2!`u3jiNYg@tc%{gGqGE_a@-C?_eO!&hT8 zkYXH6jnNE>+0Q5?s?X?J($hgJ11i=FBrdTMoZ<6JD&#q-d490Stm z6mN{hm8_j#V$Mmc$A<4 z8X?^P4-ll$;OO+(gSTeMJLwMC+C9XcM|Bn?e|qi4=eMpFG$)vj$qA-|oqDew`t7@h z=~uO!VLEs(%o(PGQT^(Gzz;-=zuU-LZTqHn9x2kiP89U`buvW zx|`^xaqOu=>P*$8E4LvXB0^3~<8gvP&;z3LIVly>K9yWt*`R$GT?STao*lfg_u`uw HOAYu7W+SLD diff --git a/.cache/clangd/index/xxhash.h.11DA710B069D4A59.idx b/.cache/clangd/index/xxhash.h.11DA710B069D4A59.idx deleted file mode 100644 index 349da000a8e8d2003889f7f855edf5d69bd00e87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124706 zcmeEv34ByV@_$bPgd`IJh#^BVFopmLLXw%>c!7wDawx%L*LA~?3}iIP#LR?a-TnO# z1w~O=T~|fL#cMs*`@Rs*74HjgQC9E>q9S;JuKvGO-Thv_$s`2C{q67f-+a)Q)T`G= zRdsb$b$9i&iH9CK=wp`{Uq5kPQz#bfA%qx!|Dv(h@ROVI3;m?|#?EXFhi3W4H8w`- zg0WE47i{*8Z3X1`;}0(l)J9_f*2ap`fY};~wY4_;#x?tb3cKFd5(ziQLan~YtOQ^A zBF(;NTU}i!8ZENFpE2c_Nt0&KyIH|-V_R#83vZ6ZLcYmu(U@;$$TzDs6sq^l4aXW( zGYC*aO(`g3>}V(?>qBos!KQHY?Ap-0U{gzD$d^~6LB0tRl$Yk)Nk=QncBiBES9_?V zC~(hpbbPR}uC0+->JLENlD^?2QZClYbK1JHeq^g&BR|%sGU4< zN}<&sqG?5$)wYT~A3-!Gh@Z6;Wngx+ zmWEFaQ!dzCUxQ{A6pr!LMVe!+!Md0)9Q9GNqQNF&jy*mSh)r8_CxT;h&5#)qA*S$5 zXq1ylt{Bp#u#iM_E-&4X6q|@7RCQYP>wQ}E<7IwMu(6F558-!)1fKaI@t_h^3W@C~ z-%N}Lj3jy!^wF3?{~~ijtqq}IeVT7pq}3N~h_sSG^4NjiO9L@LZ2SwI(iTR&YUpTY zme!(rgJHxcsrrIEV5x=~B{N3(8X|K;s40!=Zwk&w8|I*8jp5l1vBvori7~3RCDe*W zGzFXMLcaR85Xm4|4lR!H+R_?n3C<42!ce^^6zos))rV#UF=V)%V=W2l*rHKVw45(! zRFY6dP!u{25rZPu>Lm)EP=VafC|)#%YC)Trp{NVv1o^LK+VIVrg#*o=nOgf=a`u zK0Iqy7?V-V<}PHlIoRlfCc=>6N<)|*s8S4^7={Rh6z}UIjg8?be&>QQ0|hZVK%ncx zEXAzEF!5mSFvvBMl=n!Qn;L_yvqQd6bEIu{gRcO(+#YoC3LO<3E(h2s?#(G}!NzcG zz8Wa1cwe}w1x=Q!vpyWfXrsnyB-Zi4#@UfpsNN>oWSCMQfh<$D(N|0DtMxTTA}9MA z!zZKDP%F)KJO{TzS3`331UPO^BwX*ygUL209Bj<TmdNlQvwh+dDl(2)oSZH3%S0AZsqd6OGI<5gsgC3!Iwm#A}Gv*73_3DDK zc}7Wmk~QjJZL|q3u0<13TXYnR0u&8$ViGbWQ&Ey8A?8}Bo>j&#rk@Qn2#kx@(mYPU zA`Dw@wxOSu#W`KlVb9nPWL;2!4%N&R*%lx8qzqJ=#-PsW1EUwDqokopEaDC%GB&A{ zR!pn%du1!XjD?xpDCv3?`6f2|#_zi?DJ38W8>11Qu6g8iscIVMkH-k4 znn!jqxhMkJUW8>>;KyQ-`!!Zdf%xUP$k?->gb4qVlL$>8eLEdRG0UjG>x%Hgm^ z2Fo_2t24L2;l}OUEAZ`6zrd%*kL4NgVD9K&Qp1#U;ArswoNu6%lGYlSXIMY|$2H(N z&G3!8(B556Vr+J*IgEeTQ%bwIkuWXeryRo&BTZ(wIjq>oI+l(CT)ytQI{)5bDA?2-Qqvnk@QoEo zqjso(iOME>RN3+&L|a0wW72%1Et8%Cv3VSc&GVfcnjiHQ>~DA&lP4Y`1Nq~Q_Z{F1 zl$A)LBB-xQ;IIRi!4V`~h9iJv6vAs#Y|cp+rIGv)L|JB07y&^dS{@+}0Rb*vv6!J^ z^JF_=A*|J>0n%ui0X?lD5(Rzk5N_+}`ouRF#qh)@y%vgG*CD)yuo!%sx=>4u1AhDw zxiD}79i83Z=l30XFhy&0TU6^f_#GeoYYIkBhSy~KVUhzBn~>ClH#@f>j8Ii8*}HK3 zWN47$Lln)Dv40GJdMW;4-tCb{R@y{v~cB#IkhZ7$MO17^bDO_N@t9`KfDI}&kgiq?$e2E_Zg znvjyI>SL6J1|eJ@nKcW6E`)S+2nR7fDLq=4h`xD>Niu$cs1U+rCx=^F=tFX9o5|e< zcUnWbKe8z@ltx^Ukt!J)wZbntgw__N@L3f8E`n4MUL}gia0eBxWR7AXIyhnvM|6aX zSL?9W>aALjA~!)w07@b;1g~@~fJO*Bbc99tos3aQzR7CQC-qjh0g<_mazH@?-&6=; zq=gu-h8~1ISWo&{jY+xog-~B82zjKCoeJ~G?tvc=iJ-5%WSFn6fmc${;pp68ONS3+ zjBk_=!C81;2zYaNn8>f2O*&sYdNj(cXd{-lonb#kTOIGCHsx__kHfrq7Ehv}lnMn( zNl~Fbj6NB#RRvKrT?hN*!W0cgwZfZt!H=1Gpq|tkBM_-3#Am`Sw2qqQJ0=Q#&8V$y zLxdhkwXqq@lo_>czye@~4{@mA94zX~h!qEA=Z2xh5w;|XM&Wz}v4Dk8sf_7cLAS<+ zsBe_775|?OF;mk8Ed>|% z!U~2St-}Se*h0fKg18&4vPcz(1+!plqz!`rV_L?fr08&{ya5rZS#314gqs_o;A7b8N9;HFZSIc%>GLEN#J z|4D^nrK2%cQ5K7Um<5<#6D~8;_nO$OO;8a+b8I5nLHu~*iR5HYnN3udBREWTcN!Ze z>@E!&!~~^cAdcG`5lGm(iVbsze;K!$G@gzLb2WRwr#6S65meBeZoH|} z5JH>*4w${SVQz#bYG`eRFDVhF;U!)920zGiU65{=b2hG#;_&NwBC<#PDq#WQ9`L&4U%2K;V}>;@{?@0%2w6&nqQ09!0*e+(1B zAQNx`zG<`_s{@kVu>(uUNQQZ%%Jdy*{SmOS4v53T@~QsBz}i0!%ZC_;Di~OXtp(p2 zyFbVRw=5NaA>Vj%6?q{XEDkp#P(r(h0Gy9e#L^^1 ziJeZeD0#Hl^ReE7Z3J31$lB_+yCUk?s{J1cl>_d7m4WyFlZ5WxNcz7i*7gLq2PEMC z-X^Qvnz8LeD2BmeO@B1Q{b$BjXF2|D9o{`K8vZ9{Y=m)X@1AWvl9`MpCh73hauhY_ zYle%1g9^HjwvleiYik`FmnKqNK`=Y0KKYSt=i$RdCZ4TnXauNl5fCQtMaL((GP@S1 zOJ*A`<8_C8)0|FQqSIyLWh*;SMfzf7FVm?J?FTxDVz)$fk{43zv4qtnY4ia+6b%y> z#~$PwBXv?vBJ8jc6-?sV?ZHPDdE$smbtfXO5qPm1{fm(gE{R=BVwp00W~?mQw`%fFjAwS*ByiD30jD^mzR+gEC~gY%4i z3QqD!1S5EYVNyrOTVfGgOG90Cibwkn7}J{eFo+elit^i^cU0IMX|up|Sg|qKg7Qpr zvtz&hZ*~j9^}nB8hwynQSQ51AIvQ~q^RN^jlA=Xrthu+2#vur-71z@gPlxJob^*sO z@F$;-tiiSmLsaFyfrHy<8wzdQ8t0pfb8NJfkK<_!qy=t$7%NfTG7zK<4_O``V5H)F z{2T4c;H@aK9%GT!XPy4<HE&^~&dd5g&R}1Y9(|ZW8b%|Ao-b17}V_6&IC`^uG zu}6TExOIq=g#-+3#NLBO+9HBCF`OvFhAY~{gytBlML5OJr#AKGoF+P3*v%uHY1rF> zvu$)tnHr$C!jSo`j$_k#w8qHna2*L9QAhU;$3SU68mwm2&M60s13n-WT1^tImgq_4 z(VtqT<;Ec?mr;305IZ;$j}DZiN}(VzvxJ2KTTLhqPN#(E#1$4DtPB@Q_APuVxVj4o z8H_o==CV9@fcAD^tzw;GtR{we6bjA~9Yu7A#3+02>3Gtgx29>awNIAQhM1V66rL1p zM0IqdBOdKZ!G^(LEAOP^h%gT1#)^o4OeMuT(0hiY=M2Jzmca>Mz2%Q5A4t92bEb~t zk}`piG|I>_Z81V^4| z9ZiRAY0nKGtfgIIXg@Y40Z|?$#_Et9nNn_WjEs*ZH-$n_n%Ej?lS*^EhNTBbT=608 z$g($EL6ky>krUmZnKAa%dYja>hVr%QY@QFwHqXYvJh?}ckM{G(JwvYH^UgDld9eV^Eos!j~6W5;Pkrr(8HBcUj>)h9Pn z5w$Fq@)jgG_92LNBaL!PzhIp}?dYsm{e+1mdn(NpV@oX$H7yKX@t9(JqxqyVt!UY5 znhQA4R|1x_wP05owUM&Pkm9hhXsr+1sMORqGX!deBW#o@mmOrQ4`ILXSbCmXRQCry z+cXsG9F1*Q*xMfqgW*U~!+{;aST9Tjr)`@STvp86pK7L$n?7-T?WBoQj+{8OFq#KM~@MALukbFJC~v;7uJiAvXkDN6JD@#C9zFoXY>F-{_rEi6_uHbbQK_ z6Zf-Fv4UAZRr&Tgz*k!6I~5YZnSuQK`6MZ+JT4<{ut*I7?m?_0vv4MyW-;swRqe)$ zSS$217k+~O#KJ+Mwy>EI4o?ZQwP361U63F`Cf=R>x)) z~>8JY129OEO6>4rJ$wU@F0{{O#+)AbAu*_r4kOAaOk*WCdq6_ zaz?eoB((ip|6qC?knK?bOpS-~q5L|~1zM6Mi#~u9s!>38=Bzfh6yY9`8-*X6ZuGU$R7;h2HXWL> zucpLAM5SUo5cO1x?$idRffn-`9)9N;5j&O|mY z_%E24!!uiht@APM!*IghcoGvSk$A})xgfN(Mas%CqraeHA}wUE@v$1p^97537HZ(R zCVqB2cEYrpiBqT4PB~`s!4r_8O&-YzH_wTjY&XL(yH6wEtdr@TH0I3EY&yn(gFSQA z@|CS@^^{MFmg8DvK;bkMeaF5WYEsKUG$`2V?sCU*CklCNH+NQ{ws?Z1`O$g>v|BK|(C(NmtoNCFQt^&1kbK1|&8JU1ZW*G5ja)?-DMRvJMWW`wl zTbNXOc+vzn6W=`ht%>Nwlom%4Ryr~I%I-Pvs6 zHXIV3O(*r($0R@6ktS~jt4S3I(c3a10Fuq!Gae0N=|8NL3EOssm55CkG7>+^bP#IS znJTB4-Q=n15Ol>?E@=`q0rDZbZW?E_l4k7EF(WdT6%iLS zffXgQ7RAROY-^5;Zqhxhe2i9^79Zi%z$|ARweCUwyk7b+SH0|#{}<&mhV5tMGsd9| zq{zc+6Itd{u*x-t?0kgb=dvp>vu!qFqA(!gL$@}DVZ~$Rz)s(3&T$3%AK;r)QdCtG zfFHw|-sVQy8tV~GMYc|8JKG|nbftBXR?5*R;|1ze2sp;?VK{Wqanc%lOM^C)@)v-c&T6 zdI=YTa`J&zrpaZDE_q_z=z|Kv8l6BRFd3s@J7I@9BaH6g)U-i#T34FeidZQ!8*}~x z*!<*#(drR`;wZ_EfbMjHHwHj=C;0yi%iVL$Xc}e#=@22X0&JZQ%U_(OWQp+*!2dgS zg!i=S{MS2hFu|x3I3q`Se_2P=QIZ*`CQdF)=g%9l>$aAMHAh-4PYFI8(hqP(<(hC4 zay9TmCzkd`Q+94279U3Gbw8RrW@r`R>uSMHElH9RF%B1Ek&qI8%5AqY%tFaDTd*EN zyQq{mi350Qi877S1XITV(kq*26lva_CQBy$$C+KA;^WgA+gXiyD*>&R)`#iXn(pWf zw4Z{Oae0lU@2Qo24L6O##|e`fLkfOD3w1XX-2W$I!OEw(X9h(Y_9^{mR7mym?>8!p zHM?$F-`yGoDm41P#)nTFw(EXq6lAo+H9u2v(aOIJ*z0{>pe7jz&ZI6f$gXxL+A&(~ z0VP_vO~g8@4tr*!GUewSMI{Sb$Hlu2qEh)*c{^z{I{{oSS4Ht@a+Jrzaig6W4vV>- z~mR2;=?(r#;*` z)s+zQr2oH!=$$mVJ2Y2D=r2X`pEac?j|Jr)AB|0g(N zPdEPt98sghnX;t296?Y14LL#@7c;jfHf}i8`#AroN?nU{o9iyWaT59#B_a z7>K}IhomAL6(wE}a{_AFRkfX}MT<}gT`8yHp}SjnMcL0Nd_r>@u92a%7Rb#;P9S1+ zXdT^^qrNf{Xu~*l4+lTc2LykK@-*v^3Y*T%r<;|U!t=ln+NDgrQ1{+Q=5Pse3$l>_ zQ*G#{UCERoG2AR;hAPvYwp(OakxU{PYHe$t8Ri=~a9a|#RUu6whaMnCai>#xUPQBz zKN3X5ULH^$VQ`jvYPAKo4)IcWup>Pq2%6H}I$)u-tc_x^R|NdE1FA^07?W8P5f7#- zJ2-Xdo-2|zYe(ujFLVlB($+|tB;3OL#OYojBAaTMWEMIRZNS+ZY`JDOGG|~oN`@&; zuuToqQZi4n3ExqgSJc3t5VL>*Nj2s%{KfJRFTX^1$sAcCQD3zaiw6=0ib8}M-8bc)qoi*wZ39np|Zl_ZR6b664PT&5dmO=WDcLdal0>UW(_9k${`WtxMf{%&&6LbUt!|1Ae8 zC#=@6KZk=TM6Zeky1~F21f8(zB6B^HiD)nP+d8JU+>dYoW6H^CI+-;1hwB_^Nf$FM z_(rx~WG*ZiooQNU1}D*&GGXczo-@h%p*hJoZOVtf7=fdPTu*0Ht7@;-TNuir7M@q_ zcI$W0Y5z8jmYTSOjSfPvrVOg%h; zk-bsN;9&nJv8}R81xa70lY;bJpjNFk+sorLKJ6`2tP+|`N)O*HppVAMO$utSNo|6| zY6wYc9@kEsf@5qGYR65Qgi9uEnXR2T`KU<~CQq0`N8KQAs74hJahgi%kTbxO#{kOX zs#NLj$L65}~)ww$2$iHLK zRrYFhpM88IVM-kmkx^cCW*7p3zVgPh8QKq!*L+|PH!U@S80tVJ=cSCo!VlMlF~_0N zw%>USToz6JQs+h>wqfme#-;Jh8weuOys`(v)MIA?1P6JMvK?x}{!~U#D7h+ig1G^a zCaadD9LX_ZO+v-cHg@z$(#g7$lnJRd9|UCLbdsS7h8fQB8 zqi8c4uHC>0uJ;|H?iq%`0H<7CwruGMIhiTTxPz%A9T3v*R?rMzTA>>l7r6YJ_ob1Y zj&Z#^U9e()yVnIZjs8+yfNGUq;>prUh3=AZ(xalGw)%+NfdWeN-lG_{5n`8$rZ@+1 z4jnhG=I|n>49$&Hp3=7DeCpvW_vY!p;1j7@i2XYYqz4kHK&`xWiF+b$J&xmpQOwDh z+TqnH_ZiV4f5pM0kV}SkN;cP1OiN!n0^fx*5MauV!Hbm=GA;_I61P-En#WlCboi7R zSrVP(h+)wh7n3<5`2H`o;S!^WY!M8SDBX~va!^RYC3lf;R%xKL-jYXjlQtVBGRld$ z8QfCuSnI07Ao7UEsT-RbDFlJB6KujYE*yP8r^!dg%`6l!s;ARjn2}Hrg3Dn{`nY(U zuJ)EeJHkY|2q55Am^waR2EM{P;|t_b1_d$g5GEQ*#8I=eZXBBi!N$2kTzZDuIYGMY z63)H_Nkb^pg!nRb8fAQ-iCylp;-GIc(pSj!YHZOm+Cw|GKtmJ*eZff}Kq0bnYDjM#S0LR3-}}@lcraZb#t|U3*sRy(5rAQ^&eEdb#3iO&^hjGr`a;^Ya_ z#(?A`i4@y7xtYATkwj7CYNCyCkhGC~ChSo0dQV$)h z@G#Se-lSzg0CY}5iyj-T^Ap>%$1gaY~MU%B!A^48g_YPQY?*0@>ql ztFkViHV9*%FCRl&k=X!-5t>GcF3>>;2{MR`u4|p&5{t}k4YuGqIMr0%xmb?`B+`zC z4#2>t7DEm4qEQUXRI^|v(N+k=zi_cvT^L0p2~aC}b81p{T)xLg ze^K)>lgEvxUJ)m7)gAg7_$`7Xt?&=3+u*_U}6av;3ZO+gY5OVJi?N|!VBuUb>Nu6V*{7@E^j(KaR z4#bAug!I%ZgvspfAyNcupC&l6;?h7oUS*O_C|o}k`$WS#d`g@`E18&>nB<}clqA|F zo5WX1Vxq!L8B%>i*zCy)BLQoOEY8GoS~(%I5kKX9WojjLmrH?C*`0E*o&XBivHnRg zxWH}vKTNxe$%_fs(cuoc_M)dA-j*drTV`UCT!TPGV2L#8<2x$C6NXJ(Scf-h%S1Ok(#K9!!}Ge2_nr3Ixj;~MJbO> zVbMkm6fKRkJme${w>9Z&R`=(NN6i^$+_A?)jHEqGa~{@q&}W3(<%)ppj%6GEi%E{d z+t`86iJYi2Whb44gFiPi_l<=CA;WQYa z%@hjyNkQgq?NV@>BUsMl+XHpcxRaA^4OD{^SQOfnTRmvceG4@Zvp={FyNlGA963Dq zVshr>k|z1lB+^84^>~ps3ItpZ87@I|I^;`+0x`NtVUP^I4Aem><;CObBpGC}3|9 zfl{1dgXFV)yx=|AXjQ{W%W~!NC?1q}pQZA41v5NzVqTHq=&$G~%nFyDr$+}p-Vvh^? zPjVM7ZV@Km11v&MsVADhp?TQSqHgZhA!*w{lJ+mn?5&_KeFtvC_26ApAVe64T43ok zLL%@F(<-)q@Ub`P`XGN|1DOz{qKQRgd{bcq($JzUOIU+c#FBKBE#>6zEPA*~7%I}5 z-7r-lhX`#33Beyjy^u~Bp8>a(O+b7ZM?A64`_Q-~7aoIGN?(-^$08*U@C$4yIgpQH zI%Cb7SCSBVhauG{_xbn^V~UIEc4`tsvWlVGi?B{V zJKBQ#z5px^l=#bwOR9=x2n|#m-A22bXlK@Fd2>@yL#zod20X0kBw_eRJcW;7Z5Sez zH9G`F7Hw1MY^5E*yA^nQ42zIT{7HVLbS*G5ldb0{8B-7}8xX}R9pys*wjesdCfQMN z4nn>ONZ5!qYbdwY)~0z)^U|O|tx3YDoop@|Q@rwI3}CxzhebuH7L4zN{WkT=dkV5wl}Yfjj~WvV!pRGuUdC!`AF-BTcmkD#b}#9GjtFVE)iNOUbrI@-K{Bs*s&Ist`P&pyD42g|5d@5{9WG-~CAB4p&v9o(`z0 zf%NFAM1>aL4WzBCZL9K)WWTP~?`XC2U4Y+}sUGQfsw+g9awo-z(cNfJgRC<^A9vcf zKxYL}qC$P!2a(=ED!l9lajZzK{9H!u6}>xQwT`_KuXPsqE?Vdm3~UA4C0r?VBw*sg%r`mb&@7821NmfF4BIY$j#b!@2{?TF(AI&CYL zF8P_DHcKSpiXgaFIg-uB@GKjn_$*;HVEV2%TeAM=i%=Xj%wmlum+t8%@1$&^AIqbY zt`%k7B^1l79tB-dsj~u6ap(HXZdF&x9#~9Wr4HpEj)ui+TJwvYO1}enC*q$Fw=?1G zWH#)g3{z?nYZxpNXi7`u_^x-DYtjF%zkq&qaHu;USp2ZWUUfEcqx8zLHYJQ1Bk>2I z(XO>AAyFM|lEO=yx|QhIlZ2B#T_H++)Tyd7`Ioq+-BoD6K*`jUE-W0gMtM=NW5`J= z@e9kAF+Z`zH=uQ%b2{!GvPJO3w!$tJ0x41UlQ}`{N+Bk$p?eceMV-*s*1cv~iOIzj zFYRcBq6{iso34@7xs)+|^+4UPEi=eJG~ z219hWO)#U8vk1l5G%w5sF*;sG6Wn|#w^8_6c$2~71#Z-K)K90lax z5>dnA@@_Kie~2(Yx7jAmtg?1l(5)|blX$z)3PYH*#6t_Qz^ei9OK@2>w1e03uOgU3 zJN>I#u`}j$siQkp7D`*$9@;n@nX&)cedg_)MgM{ZGjFg+z`-Dgxx{apK0-) zV^c+0F1ju9Pm4J5qdxAnO*>}*$NFBeMJa{2ulG>lQmQoTa4>%N`toy{>X5#jw?tnd z#qo5>bj=y3$V!6A(&f!5wjr5VwmSVn3mFITYbcCH6+79c+hq+^^^pzd4;44L$$DNf zdZE=wJenmG@qF)MNLo_>DP+m$F7%RakzIj?NI{Kjkr|KDhzygRagKd$#P3Fn^bBjz zNNYs1MTE9{G;+4oD#MUUOG{a&r1c=-j_-ceNi-nDvYmB3$C#4UIwl8MT= zxB+b{=Z~v&{}WQDTEFccb>hY%*xzw10@x7^`8nccw85UXH1P@K99M?vW+54?j1JhI zALxw@(YW@s0undO6C+V$Y}~2TL`l5A3N@ zX&&vm-UgIUb4h0n4E(=0_)L0SMLz-l-6p$X!XH15b_G#ErJ< z*0PqM!`=Q;4(v`uU9y?>yv)a$HQEQKz^dd$$|h35!e;($b2mmXKVNI`FzXJ>KOy zR98tcby%U>Ss&$SHW}Zci9nGxIJSRvqhS6DURSlW5ZaLX}mmqsV|TuHEoT4T;GYroy<3u=p3 zXnF)g?{uQ#xv7bZ;#VrT+_mc6Ss@?=IY`|r#NxBk;5(69O>3&am|Jv{;!BHKo^w2{ zEKE%_g=&gOB06d`a7X`Yv4q!@@Iu*;JB!`G2zMgu&LI+Iy=bw1vs!G(q^!w$4Tbk8#N|?MWMellJ;@Fh9LcNkgD?j$Qv}8q6M3yAidss$0jFm)qn%UL@j}|D2 ztZOE*SEx3qM!NINUZo*b>(qDy>8zI)GYvL(N4&|!i(61Oisiy|9V{lsOXWW6*Yv0E zovs-cn$$;^_H5`T%` z&;OTC`W-(6q2Kgh^echYJ9^MNpD3BBaQLPCj4{>MsvLSNA*v+)oX}Cm0p0#dgD&N3 zmSmRqf?(0fML}{N$v{Z!b}E~VG@q(%lr$9 zk?ONiX*jdl%m+*9_;VfQ!lLufe8?0>cdfiv^4ug27`MeJaX2X%oZ~zcM@>WXI8zl8 z9dNEDN;r!{qd4d-GwfJrMD@8s2}XY z=aL? zjU&iOz&W-M5s^ zjw4$%=aOdO*7ViaTLRsg{*3rYU6@6=9`uQFeTH1lzG>W1(7?W`fwSu~`x@J5I3L%5 zlx`>o$D}(yCO(Qz!Rve_l%EHGa@rY{n}YIuP~XvM85;wYWN*|Aul`nt-NM z;x@2>C`I~5bvP=EVZzXj8;v1KPZz6II>(n+!$4mR-G(|rDxy4@jEWLCuwsJv);|vk$#y1 z!b;r0W5VLHUPB?>)5a4VgjIb#W6Cj;CLs|q(uG@hzOy*_CgYF=jTR%Hifkw7UPDV& zEH+GjNjE3~bOECnmJELTd_|=bDh`06Cd(F#?h-$gEtYlz4&0;^elb(X2g` z+#)w9E^HP@O+yaB+R4ZJt=|ITD49Hja=5S~hv6q`LX^G+x=5)9pPVFUEXx_a4oMeD z7APYXsd1jf(8nlpaQFnP2yK+HsnmkJ4f3K4G6L*f_@relL@U8q8d5*epu+@UC2v4u zIF|_@vQeo&L)NgAnJ9E(1 z^#?6qjjOARsz00P5-Hive*6lMAsuED5-JC{70zoL{^cZ&rBz_eQ1vl3TnI<*zdKw8cF4mC%(-qZ__@z7z*Bl336o^vSz&0eZ zGhGsZK^O@oa$Db#-mn}ad%}~QZokU;Cx1sv3-o-K3DxR{u zByJSF>7-5cs?#RfFFUlzeAQXmx=OwRN%kn7d+OGQ&R$x=q|(kvJeXLuC9#OV!eFZn zL(6P5f1o-Rfhiw{X@Q-{l_(U0nFf(jgn~LzW0BH$JOVk8%3_iD!WiU0Li;*&M8YgI z=XfVw1OG!d5Wfd3O@KSx=|E;JrO2%)lL+L6$Kn_ylMBiBau`p8v!!HC0&3KSM2;8o zW(-v4;F>Y;YLTE+X$qOtR@NbZuWb#@Mmq0Sf?-TKlqCL+Q<8whD@huNYD}v#Y$b_z zrjo>;4x;J5t?mF|X-$1ag;eEKv=d_Q1345|1|JShkqD=$B>|KgNFP~&kpwv5=!8?? z>cqV%WjY!S!MtMaN9m*;gaY%DZH*4anb#(Cd?{6LM>tt_os_q;zM(ZNd>sq5Wy9G>pmOxb=p6>?K^9d zIf*#h42_bAz!KaUebw8<DuY2 z>uS)|b~?6dwQp_P2@W(07vze>;;>t)BT+piKd zr*^`$X;Y_l=mDJZj<66p%;c`}E9K#uzf&l|{9C4$wISqi@?Rvw{EJK;Dt|?2t~3yf zpt;D+t_B-~>xMZX?A!GIq;!FV&td_#ZOZX$+5uzr#gnd+wy z0oP|a^86R)Dz(t(MOyic395qYCuZXIs8FkVAoD6bB~dpff9!;DW8>wt)}|z3)O5rk ztCL1E21nhPkwEEChCL8E#jqr)lOim^h*A;$I{H8VZw`K&i2v~9Q!;Ap%=`;mP#k}r z;I9sxDD;Xs1^$FmV2D&JwL1$DNF*999zUk|m?%_BF+x|Zql?G2v>bxaaq%RiRBbG7 zo)c~wO`+uCCJKQRw;(4h(uPKhabH05?0WU64fu=vMODR(;h7Rr9Pk(8%8ACd`cQGG z2_Z3xC=@k_e^s4{t89wSk5-fwQ-p3Vp1OgFTGt6GUu6dBD7w- zwjsRGEod5RodXj_|AN7vI z#tOZ%H!aV&^^ZOJh%b|GrxHKUNc%h^TR(M)|IB-7 z!Xee4^>B+na*21B=iXh820oWI{JFF$;eE^XJO9=sly_WO-<8pASERVb)r`2to4dwa z(4M?CW%$;VfbhQK`h6)Q4nOz&^PfBVj^62FD|dN)uL0|O`P!4^Q>4nCoV5MXzlNXK z+P9C`ntTs6?~07RS7Z#*Pu;w|*F3fPkcQ@deZ>2+7awH||0n}JmQRiLtCK=+U46&7 z{nEwLT<3zozy$#`PCj*=laikQ+Zo3-_U|J;NM7qgoe#N(JmemxpSrLQZ+oq^WS{f; zyT#XB*lD@qwA{@0Wcd`Ht*$@0_G21fJm$jzbMD$YAYI(Wn6LHN`?VhXv?t4_uJ*=H z&ieh>{T|!fEmkq*UI}C4X;>=r*V=4Q`;&7OTh!CnIv^x9W=A9K|Y z`ab!cWO4f`9}N6v`eQ@f;u`MFBCmImcT{`wEh&RPwv|u@Z{5;!$0i_fqBz4KX4aS-B8!RZ}0JyK>U=e-cclW6!mRSmQUe1 zJ?R?PAzb%kuYPpVf%UPxOz{vGd3jck%d@~4`J~ZwUD6{&o}`n})f?M_lB# zp<>%m5K=yc=hCDHTNk!! za>wLH3ev@oT>0VwdoLc4+ny|+n%SouGH~0`mEMtV@dcOnU{2Y=O*3aI+(@h(4WrqjtJ)Y)5X2q zmy1%o7o|WXC0-m(g@tYWYBWcd`S-?=Vzi5rt&O%?~e zGCI29!2Ez)+|HP<4-~Ht?AM+wpTcu?(oL?)_mba87Wrke5N#o<{m z4js5WkcTyiO$mqKu1KgHRP4UrZ&rN;W^Inx(Y~bE3 zALdy;EFXN~r|^95y4*FLS$Ny^XBAeS_qQ=_@e^ZS<{5CA2UXwFbHFVt(2cjGWuSU;Q@*Gse3b`@tNW$a>~cM#pO(%%SyeM zl|l!+mg;>i6*}M<*U8r=G0CU?xMg~uSEA$7#T|_KN^0&aso;%#lKhlGewy}5Ua{A0 z4*}~I##-9byR;{yOg;%~DzNIy{xI$FK-wX0aV2B5=jOKOLW!P}GW?tr)cTR@3|Al4 zQ5Vb!m$!|tn&1}CGUm5~#J7Vms^wFpw!6+z1LnLRe^-0z_KCp!CzIy8?A-6NQRc#) z!x#2MnHycFyE0h$TyyG(J5PD>$O-A<1}^g<_t=Nr(`ZVdC$;f%lpw~x`-h7+|MtQ| z-Qp@nTi_kMzzZ2$nKE)^ijuLU`;uQz7T7P+d-nFLfcZ3IK9J>kAPaMhe2UahU1z(X zL7=2A{o9*&9QSq0VQ%pnH}ctH@oX_fOg=^G;-tSSX}s--*K#j>>dJ||#NAxx#Th9V zXF!e1CpGd&wDF+zCvSLn|4kG7h?gaoFU#nA8Of}CYA$~jed3PnH?od!i;K9hw@SrZ zr4X1#zT1Ih^hrIaIhX<_q42TbLkt>V)tt>7C8Tn~y z+uJ8^UUk-2xs%=E9md>IA$C;kjY)x@BJ~H?6-u~|7FGVqEk5Hie<%DI(bWsC0p^2@`FeqPy#Q95e2UbCNmr{j z-Z$|#-@e#((y_h7ZCvw*87UuTpl11`nkS;>@2)y&>|vt+ap__~53(2+4j8a-0Jtrm zde%z2dEfA?`~GsgTdd%+t{Wq+8w1HXH*>(bnI*z|X43Vpd{$3Gd$rG6G{o~Ox44!u zH+XY5cuO&J^d5du?*oMQZP%$eJXo*!O~v3xrd$QYzcS+GUU9kC4O=F~yFR5*cwckP z$zqwv{k9jaz)kMV{str};6t%BM*E)^&+8rEXvP z-_P`_J~5as9^oQaWF@U2D@8uZN~s28La~i=K5V-o=oU*DYiYGuT8$=tmG1p2eT?wF z;EIf9`W*TAKm3ue8tP&5bH&g5#q)m1w|olE7q0VNzvf;~FF0=8pFX%b)JME16~Lt# zeJ>@EkWZ}u{&v;I!jo2Se$t|wue!rHL{aO}O!0kPT-rl23&ep@WlYaa*v-rZuK_{QmM|>@9|8Fw-enVxbHnOW+~QHLdRe(xRu1z~K1J#(*9eu*edP14eZ)3d@wSY#Z5dD$@~JC+y2svAhaPi$l*r9K+ro^# z3yIwFsnMEVnE&K}qTj{bVmWvC>QZraDFkO->VS2rdBXd&>!iUfVQYTMdimB8U4b6un+%ZAU+{9x55Z=RYi9^?kD>XEdn2jo>gNs;UWd42oP_ly3r zdf4yXVi{vy5y-tFFaoY;@8RF~_6zR@*Fskci{Yk68eZw0bNL_K;u)@aQ>EBciEhZJ zNZsPPK&kFW4!Y~9ePPe@$ z#l?(xO+Z`|fT^=Ob@=Mk{e^d(>%V(*-FFl|*Y6#_dr>d(H%9!)o$`|#A|s#Fmm|=Z zL+ag^*4=;ZV&WY8#v3w*ZODNABA=RsGdJD0`lYA8UhEcUb79M?#qw%cci(10HuHH} z+n0yv@2j5(?KkiKv)$rW?(ydp;`0j7T0VtmwQJ^4JWTx4kN@!OS(h(O7mqXIIVG9r zlnjTh!%wY^>xO*ybaedv=exxRjI}mjtj&itEuW^I>;L2D`|Z0T>5p!4A-C{_Qt?75 z^!C+h-mBB9S&0;J3-dqmFI|1kkr%kdYDRpqzx&1hpo@G8&uK|lxej1bpK#CnA6)Sm zVZO+fU+)*!`!Qk2r%3(M^+%=WUdiiq#`4pT2IgCgd0nNrj_gnQG^gnH)R7~T{FN8E z#mkKOtzUdgWy+`UY;rAg9n1Xl6n*PI>-{O0_7R(;U;BPqzxUJNAIPWX%e6o?z1BX%%$A%mDOTpHOA-?w|9vfGu!j7=DnFh zTj#!V|Kk^Lyevc9$VEPoxAz029psa=!wRV#?k}CY@rKZIm%GI)jQD|HeBg)B%BS$G zcb(;`<+{gi%l18XP{swTVIat-@chHI*mW$+#pi2R|M>B-S-?D>Te+c1 zY^Z|5IV*j{S?Q4P_gw$AQYzYmu3W$D$zi3yynr#EEEP|dLN>Ohdbg$)v5j89{VAIF ztE3+uwskW?P@5?^r>R7#;N#R&{8vHbk z+Oj7vE6Ka;;A_(1LXu_jL0ZxWX;3@zNkg>)Lp9@xL0kX)Lh7|-)Nh1A!uq1H%Of7&oKGIT)-9f5tVgmkAISnkHTSKZJ@d?P#ZWk%mGNo~ug?#NzEr>uH!`KBA);?G?1l5(+x z=G+I;ybq)mv%C&q{)SgSRr|-|f;Xj$zjMX+RAt{&1?NgWwJJH{p(h{fS@P&j@Fuyq zr?SLTStw3EmCMA2?2Ya9J1+g=r(4|OTQ2X?oXkseAO-R%JQpV2<+@r1E(V{S=H2?q zZ*Rkl%Z0A#?^)9yBS1cd=bx?>t^+xsb$Htot?v!q_indX$e4ffc>m-g~K0eS&?CWV}$>OvTZyY&$)!**NM8v3@26;9Of-5GUirs9K zoYL!&-d}&#^C7o*L{?v&wS_eO7JiDK0!y z=3jHgUvp8Wdu-(Qv65`Ee& z?&AUM8ZsS@8*^@42l8F6hN$Od$tn-$dY;_O$q^>_)0Z!qFJ72+KVL&>K!Lyu<6!Ry~R z`_!L=n`BQiorZ3N=d6iC(PjU)+9b*^F-+UU7@F z7;{mzSX50zE#3Q6Iz}#{CnH$aH?KbG%Rh^WYx{^DQWJcZ(f2co=Evzn*{Iz5ON>_>wH}lZ@e?WMG)dr!MfES9|_uYTt)Y z;3Zt(?E!In03OG^nd06|ukgO_THs3Mu6(-i;fFsM{@Cko@d?xMg8Ts&h<6IxCgxL7K^#eSE{_PRKc3xmNsHrTB-29>-s|vX3e|#lgGWhV%$4! zaRno8uNK>B9^Tl;yRi@E;j@x%aQT=#z8{A4NSU$aopkX}#(Z!1fcJ)@-}0#y+jG}H z`F1GvTVVafSc?bky?7AhRz7LEz5-1bt8aby*T*cn=UulrkI^<)iOp4*)t|`}&twi3 z-j7|2T*=(P4FwNedC_LVe1QA>$71ovVvH{N6drQC$MQH#8Fbg)1OE07FyCd&hlYuV zhCw9cQ+OyeaeWe#XV{xptp8PX3s662)YHnvY31m(dgXGP+(BGfLQB6WGvgUYU7@W|3v z#h@v`{F2LjFGswW15-ghg=bmP?XD^=bH$5q54-cOyVkqKI>vk?Upztvr+ms0HjO*< zw+FX9mHp}OKXi+KFy_X5v5{PG`7}*a&-(0lhF?>+(Jjv5c3xO5F02L{-%0nrla7TY zgqaGt>jy0vFt57!(v6woIYzu9tH%{ts9ZjYlYf&z{d?D}IeyM{w|?ptk2B)udE)at z%n|Y_gZgRA%TdoB{lk{m?*!%|#{8Q{{LO>d!y7%lZ}dd$;VD{fbKD9`$dA84IjpglMy2Wv|qGrs!@YZ#2UCE(r?0IB6u8u!O=%@2Gv zWYrZXjos=N+qj9(=ZNQXFfqud@SK-)tLt#ae6@Mh;@aa6`PMCVFy>R$dp$+riTm8% z``jah_XF2yuKu5q1$5l%+B?2~Zz(W;V9W=r_j<4z#JSP!z0r*b%A>BjevCNJb=)0c z&+P3Mv3H~l*^z?zSUz>P&(409zEdUMse%H( zCVj*;=?Ahq+LHzGs?GIpp7-Fp|8$GDxyTQOi4TThLXuDABJse&a`mie)7!99588On zomrO(7p%>f_D*|gZwvtW^P7KMdFB@%y&_ytb8GrnuIYb(HpI^S$6bR*KYqC@3E!;i zowlxbfBg-F{({kaT`)RF1KOV-dSF@qA6!)Y)jfJ&-NPzgFI9W+MlMt9Z&1jsg?YCY z7FotUBK^ffUMb$!3SqY=zw8gZ>_5;7RuSN^m;E&c;8Xv|PyH2^`-GK1|3RPnk2C;R z1_D)Mm=3QV~x z@M{C`bm_3COZ}E3g*Eo7($lLBvKC$laQNv}(+$ANs(n{hRaydqfOXZ_l~t1rz}l*P z)>e(Srb9UERTXQi4mJQESMBq0)o9DpYft{Ts^a6SgAKr!Rh3^>jkl&OO!ZZhzN|Xl z04y4__o6ZT=(#j+$TolaDNiQ#2glCN>3w$2-Ui36$VtB9BfIDKj7(0 zIRjqGq0kQh0Pt$gfL9%WTXTosnp&L$AC~+0utEc)eOPY0 z1F+J6#7h5h2F6{b1Me!$HvkJO$`)3PGXR%XOun?@*9Kr=<)DR?BMiW0l_M{!tTX_( zRt~z=fw8u7b(0v}C0eI9i z_)$;312FAT&+!J}F;D(uo{Ga zk!O@%X}@aa;>r7OoSB@APQ8p(pKOY+l}H~^RCr(c@y(RL#+?kVnjPw`-_%c+ctfoE0>)9wM4Q893d4ban)+VZi1*6Mtwj_JowDMO%MLIASC)^svbK)q~Z!7w8YA@>VH_2(=B%1?K%_9mI4UrR! z{uY2ctJCkS&eH(BH;4MG_T#8m=&#(UqfzzloCegZcI2p6DD|8a&p9c>tOZ#DoN!Lc zYy7vv*24HonXLaf@D=Z9YN;`2$TG(3KB))r|OB<#o z0qt0q)?-~-U%hb#fcMjSyr0%rM;rk-E4|^Y^q8TL-%0=NJLxA`mMpE8<6ov;PRA#g zX5e3@UU&iEiVXbA)VrGi*pQLEAtPT$oB{YU1OGC0I0%4mGVm``uXX_NT?YPT>bM*L z*Y^3Ea$;tBP<6I4I8%is$u&WfcJ-GzdtPBvJ2aj|1m7< zAH(ttzyg221%9u!+z7Sg@3+G5wPG6x|&1;w#0;`grh7aD-) z{Mpa>^9^I{1^=KI{KE~v`~H6K`@M!Sw%(t=-d|z>zVY|@22pz*%ZHr$bH4MBFaQez zy%z)q=*@WG%V~j;rv)ku!0Ca0rw6OhV$ z)Nc%A-WbR+l)+7bLv9KjWdLpqWZo9YF@*p2z-g}A1LwL7(2b=7ZY=d#;oJ7)w@Syp zRXWMgAR9~nu(5QB%NQZc%KI-XAFO8?^zZKS%)85T3?*}a`9AlTk2U~rmLKtE`LWhY zDh0xh_`3X9JrAOcWfey*t2oZuYejLbBj2ew&Wd`qCx20q|3yWKwaA2sUq#jr6?xVc z3E0V%IagMWFaXzA4!pi{sI@`^YrN9?a%G_bc&jq!t;!Jw;HS!*pDITfM(?t!!ON;f zT1)9zI;iq3uNq|-y|-57-da^)Epxz7sv31~Rh14+qjL{dWj$DxXN3=;4yz8{P&LKS zv74*@dvnz}E<-VWQ&sv+)j@`~yRdrPh1HV{HN3p~zm`{@?J`u=P1XC{R6W{S^S~n1 zm;;P=9 z5ZfJq_Da!SX_j$GmAItJ1l(UG?sovTREsUu7RKNP-xp&hPfaJM9zdT)82h;*}B7~XCz}deo-*ti-Mz! z@wv6&;H?Fd4ZyO(3CjvCjHc=Lj4RwQq%XO}w9Ya%rTFhlInV((`o5Ik8h~d~s-8(P-GcQgRqGvqttp3XO_^?dvcQ|Uz?*9T z7JA(ay{5yq&^vmecZ`9t$UAzG1F+QVU+Og*w$yvnQtyezCl`1tF7S>w0GE4nF87+f zUE|$h{rA4)m26H<+nhXFzX7GxZaxJ8KyLFhFfOl7zr4Cg1GEuL z+?Big^*+WKFAjL&yHVbL6v?`4Ot@ZR!C_m&@TOusi&Pr9M{cs(y@LxkdF>S%!7o5g{>7YBxGOJwOgs?JzdZAq0rRzRroM1g_2v3j44)g$zlD}A&e8D6$JPM~l>iFLVs z*5!KjPAULi@b-DZ>(#dN>nHrS_MYWW?M2l*o6-B(44+ZWM;WCbWzZ&2{yDCG$8T=T z|NCA<--io_JzQ9-BX|15!eA7lPARBw(5;0%Jqvpdv-ZvsVCur2Cm4X$Jr7yk^C)X) zHf`qjeA;uE4nUxc1-%Yg(CaAcGy$!sdCu!K%vx`w9n|=jsW({TlhZQrFH;A_sT~>k zm#Mc`0dPhJ{$=X5N&psR;9sWR2?fAq8Tgl}cfbO0c?SMv>eX5R+B5JkQ*S2#;Ij<; z%hZc;04yHRd+~q)dYeB0-(_chmz`sD|GVsizssIt09JYqUg?=)(Bn=|?wy_jBV==z z$8(owm;t!o z0B$d@xV?Oe2HdxH%r%G;WRoh|n4GpT*$iE()mMd!Hp^vM{Vf2GlxIFtZbe(3{pf|K z9{$}q+4*?0uAuk2f=p|lEddJF6%-qQ^#xh$3%m|M#rlE+48VqhtPKu~4Fwe&92g%L zjQY5s)cEAn0{5o{dl`UD1@27_z~%z?W(VMl0{0g-pa;!_1z!|Y8)a-MaBs0O(7CMz z?yWWepKL7{zO|sz_+(pwdz+1cPkt)s{Zm1vR=^Ax@>9WZ1F*0#Yhj_+0jOA5cz^*o zy|B;eg?k%-#f7657nT};vkKj374BsKmK3^|H~?oCy3ck1mKM5~IsoSuy3ch0mKC~} z*#Pu*Sz*Dl!fKEmDgvJ)PIez4KM$U z3hx;ed4`w2s3K!gMSlZuW<|!Cae#f#tSB)s&Z@{b%Ym_^B4bG$VBaN0T8F7_q8yl!5VDWxv-dvmAiIuQ>uGYb%GWt;{zt-mL8RrsI=0D~sN& zEHyAbtW5o|vX6Bf3(M)%na@`bu$IsXFzET}Ar8QZ=c`9K0DxTWN@_1zPLTb)r=6=K9*8A!h}FhoZN0k|%ZeqCUQjtl~Db0GcZzz`h> z1mLbf=3RlII_jq_C|V{_=UP?iSSFDNUUm2GoRfdRkzYPMn3}k0NZO_$mOXUAgL^N& z`o{x@kX^Dgr}t9FqU-7$_v#$eiMuiDhQad|FRm#Porl69vJ;Hp<3 z=ZfOqD~hxAPAPpFHwBGXCvX)i+IqU@u+u%$wBiNe3D2=lc!GxA`;lk-N1iFVX~6h8 z_pq;Xrx}Y0cY2S#(;GCb+O^)p*LrKTLsYkDS+MD|l%c7p|Mk7nUf;_k-WQ%8UwF)k zcb&J#IUf5%Kp$T}kxW}u-Cg2W#k30M(;FYr8uaueW{kpQp*Oey5>(xD8ueJaW zzII1o<+v+`k~jNGLFy|7=8U|ypx@d8pQbzdzrCQ}b_d}5f_~pS0GAZ@yQI+iM9)7x zz$$gHS(OG>eVL#8Wqy%fya3?NVVQRh+eaT*1>pOV^zTc&`k0752b~JxQD>i3Vc1*t zxPQXq{^@!-0f3kN$G+?j>D2=Oe(?AG!9PSV1pshH`M@*EhwJ@s09;d^drkSiI(AMN zRU^)*s?hOf04}N;aZy!;jyD5vXI0-jtA^)6YfCtVefvF`m2K^qhL@dH{A^Z*190@SS;rfI zAF@(@$m(Nl%%#1!IX`6OIRFJeWEC5Ly9cJ+J+O~;z>v-q4!L{aa0Bqvz|5xxW?B1! zX)Cbzse$2A|F5hw0dL~E7QY(HSe9fB-Xx8s!Q05#fURXTl18lA z$<{P&Uel&c(=^?emoBe;eSK~JnB71~SWE!3#4%$}9s(}OhD)GT;-NU$ppwN)@?Zuim*i0dT$aVljKpPG zxy(phkquXvOdrVE59EBZ416dX9?Bd6k7UE67=X4^2S!x~zDZVW)q!tS$4p8fu`iIf zk6F|03k3EBhGUZO2QR+8aM0`m^)oa%#XLEMIwKVGhGIVYg@$p_<23X*qW}XQ7+@BP z1UZP!LOY!19ZmxcFzK}$51#tQ zkbaQ%LxPgl0~6rc8I{5b%*R`eKX0W+K_Gh*Q9pZ(ye@%r}* z?a;cVndYUjMGe$!b7uVJO!cgC-w%L&nTCCt)RAz1reQw=S~9a*G6#_)t(gg}naK>u zZp|D_z=SOQge(gI)3Wr_7%)9sKRugDJkHiXX28R|_=kD4re6(=|7suwNAly30%`@hRv1daE@8kfrj(OH-brByF4JE8V9xP} z{r}UgE|@#wj2&^*)h`lsATl7Jm0C0cGC)cdQ>o%YfZr=s`;UY*iTnv(kY2l~*UsbD z2f!uXbcyHj>w`g0lAnULMqj`a%oS!x%?yNL9@I&-&kTf%Yyg@W2#>7x0o4pdfMy0l z=NFLB%s|*x05mfY_6z{c3`C06%s>oK%|HZbW*|~A%?w29p_zdQ(9A%jVwxF1we(u-kC~ zqb^`i^+k-qzreHWu=no(m&3nn5s?7w;ZyeTxu`$@IKZbL;PVi?IjiyQ|9O65Uqv3o z86U(^_t40p`dCG@)Am^0-`%c{=Q{gIY9p?t8n2~NWp-Acc~&0XeBNAO*jx}bYOcC> z<+JC1cTrNcpcQe(6^sfOIer)`calMwWKfSF_x(VRlp5YBHT;BtbAobCpc3Z=;k-aU zJ8i;?ZNm332?o^p26SR1h)(2BXqHg5zDUrV$X{TMuSL_rOy>3ZQS?1ZGH#ZDM6aUnje$?5&Re0gza3Irb;V`A;)+@upbTyb5fApiy^_0lh|DF9RlWx-DeEgBkA@q;E<#{#DFuB?hFIkBwZT=Cd<0XGA(watlP+dhLElS zqT|IT*s~$sSq5AT=`J#$GoY}hK z-Pn~*y@$TGn!dJ1_lV)!)N3>K+E^g=+KNc$-nN-;GZMFL;%!Faikx#r9)d|E%l6Y} zpZ;+;4pa=*X+Tv%7?%J5tkoPN753Q`wCIG9Y+>1`g)PKle*C*}E8jM33#%?kWhv&e z6uL^Bo2s9i8r^%jb?`~iy&o+dY5>udad1qRF0muQ_9EFrkJOvP_2yTx7yzft-#KOe z838Ahyc0@@fK!TaN^ui#K^byE89~6@uwiaEx=6r{XI00E#8!;R!b;;*DEm}sC?4LE zFJCz`|DCMisxhddzp>6-eQ(8Zbq#nS&Uk@Y*+xR5MRJ8kXtC0;Mya=- z8hPZG+eWHAQkP_?WBj?UBo$ssq8^3Y{dwE{v^6{Y+zvn0aT@#?4SpZy4#hS4#YX=y z0^0nJHa|VichjGH(;szpL<6R(TSsC@MiN7h+zp!U2D34=6M$-kt5#g-S(n{!+1M2s zZBMJVn8hB$Vo%gN4l1IV4@gBc^8o>x`G5e;d_aI^J|I9d9}u9K4+zlA2MG3Z!o2_b zd+sypL#)&rD)mu)1&{Nn%8wQhlkB=w2&M`l4K-zG%prk0B-l|?25?+(9vA2dsFT9* zlfnxqfk8FZ*C`T6Gg2k>b?S?-iB+`R3;go)zx?{ye5lB>IO8(LaWxW+DpIc z@$@aKi|6VXa5|nl&4B6zt~!Ax*`L7eXTXI7?g9f&ByuMbX_B5qu7?37My|w2B`St5-C@9No}0~6 ziE^GRXFwItRWV?Kz)fJ>bY}|OOa_z*Tp0@_u1un(?2))V3^*ilhZt~1;?6LjP2$=Z zu-c!z+D~WKF|uWh938?i945=$WSJH_Ug5?o6iijPsSKE@a5EV&PvPb<;8TVBlmYh? z?p_RF7}z1OqZSGM8X2GPRXLvMIG#8VT>$}{NE~t^F@)~}fb)r-^NGVSK3*UA;rYa0 z63~-4tS50a9@Pek665nF#y8Lf7(j*b#R}thFb*1kt;P{sjW1&uB>)GFuN^dgAAN!W z>@^MFYkCR8wE$=`z0hR(Hl8m5&}|ysZF&NgkB;ZRa z^Gj(kc`k>gL5HOPc?^wGeq+p68!~N|yzLV0plZLl+HXgXRFEi^vy0_IG#LXZk+VwV zA>>icknJ;M5pBpIu~B|@qx?E~wiA@B3Ca-iY^N!Mrzt`5C}$}JvlN*;?D>jszCs^V zxsqM36q1L%OnH8p@&-O^jB2cIBS#_`#|Hh}c*orMeB88x8dk;^RmKm+I9LFxG%-t{{XleAHEv@BJTeH_=x8|;+>?}8N7K0 z&oiKK1|KG1HeWcK50i?O@nRWID^|uoTgHEjRIHpYDCYyDVypP1ReT1i*je6vmLG-{ zLst&<)5mTF!>5nk3dYdR>hbePIOj;Gf*(Maqp-^n#&9+Ox*Y}GjsO8Yj)EQr^g0T9 z8F1TCaGL?EoP$<5gJ_yWM-{cIks#5L@}a6)^ytXHbr>-^0iHj*aD@k!gy(Yg%Bp%Ay59LBMcta_Vkk>EdE%+t? zsO1f{dhJHha1p~zam@eq23l=on09Ym% zmI*1i_5`q2Fs~J?1nd&byBN?a=vxH~9;SvQ4SD*8JPU4(1E`erm68R&2LN{Z4LkiQ zI1d9j>o=eETM6j!n>!fL?KgBYNy=n>nQXy{8S+>u8&=9G1XRg}Dh4zu`X)S;PKD)akG7NJkN{tqRgi>qA_9h~{x4#NA-PN+n~Z z63qkkil<;6C{5r>6P#$1gt;UV%so;?Jj!B%)@kwEF)`GvQm|Gsmfxj9;Zi}tfdLZB zgh9)M00GMdZn@wfV2zNvM#v;!tx&XmnTky;e`SdI3DxP2iJ&pAJH;&)?j z`xn2aPDCwn#ug?JOC-2)BrQBEE$VUP-?~$u-`x5ARh_TJL9=e<=ikbg@$d$K83p+> z3S>Or0AOZ8{>&IacQ@62HWL0mR>kes-0jvv{3Jly?bcD-tuGU>+gh~S8bXgakl1Z~ zdbjlz1Xye$TF-!O_N;9TsJCa;#{kCu?+0&hB;J3dIC#Iu^cym!-*BAo0Zi}r-1L6` zfrqvLT<3>e=c&XEUbw**ktDr*xR-wgle9NZ==lBZj_;`Z6u0Ayx8vx{6NwZYDIZ3a z7D71kZ}nK$y^!f%D2ssg3b$T~I%D8`uMY3XwFD7a=&EtDX`F1uN0ssY-yYamJmx>t zovR5};{+?+-P~@Kw_8V&Vs=||cU$Q>(gW5J2dtxs#7#c$ChsBOteAdQq^-Lm7haL+ z?EOGae;`LC@D1s6B1%aY%@07`V(;8W<1Y;$@bjI_5vK}EA|0pmpBqv!~p6=L%nDrV83YC&wvA>;Q#|p ziuRM@09-^v9w$ZRq&Sj*c9CxvGtqY&B-+JryEuw~J0gEa%tWtAkhmj;?}(!anC}`q z-{nQu0FWqi4K8EAc9&y26Pjj+%dvw2tuALP6Po6N%XuLNV2rcx7L+o^tsmny;vs@Q zAdGS65>V{c7c&yYZlO3P0hwyA6n=;ykF{>&T6cepCDsQD*1FvUG`Nio?*6D5fW$bD zW1J_Ofbky3cm|YsMwWP<#}JZ`q{Q=s63=@CG&d6og)o|JBnok-mG z$oD-%(Wf4g6ng{3-eDw|&tmWJ#olL0FrPKvA#1!oT+2X`HQpE3cwZx6gST*lS0bR! z8>;h;z;7)iIqa1Wdxzq;7C@&ryVE<6#O}H0_22V`@#Cvj%r~Og_Z%LG2Qb4|FvI80 zk45I0;d^d|?_~mJ`3BGOc^UA+EZ?gHl=_C0`g{y{vDEh(0dsubIldqP8-34h^u3HH zbf6w*e1gvlcG)Ko(Cahw`YeCPu&6NQ`)s{Ffq*+c z!yQKAp3iWP0b~5OF@7N$mluYVKVzIfi-7U|jPVSZ>^DyK_a_o-{3&ZFgTjp=enW%F^rnlwrZFv*{9|tl%4rHNcAV_>182WMGX#$!88BKvK zbPNQErohmqz|#bD1qOBn3elqnB)S44x&qG;a3hd*BalJBgMjk^6YA(;!1<5?OM}8v z<^?Sp5GgKg@ApbA^So;2E4d0^cn&CLxuZ85(7r>4}Ft> z^P&9np`vI2S~x%#7M6!|Q7OJ#e)hl0oyuDlSXGqg7|L^^Yb3P|Iq?-a)FM!osgqj!Ef zVdwYM<=y=_xBsg_M7JwGD8q;D-I<*+qx(mgy)?L(+Rnf?mSFsvtcVS_! z-(R$C?CI*Q-&bpNG|qUG@gQY=9-&68&m#uK<6zYuxiBk|Ev(vaiQFwwz-0q~CKuP_ z5{R4LZI|V?%SoK@=Hzj6@~BtdT(4=am&Xqn>yM*WKl#gNBaEDPH-jS=)B5C1G(C$#fVsPxJ#Y8SkixpGuHI;jLTn<4o zZNZebU>45R(z}=c`e8@wJ8{s?CA@wKZ$aw=fKowUDp=5~9l&}?zh1JSnd*;c{`806 zyxj4Q>SNm(XY7p2!y3T|h+L2qApo?SlZeE0#WY=s`d6&@>F13#PO@vnFAX=%KmYMJAlK$Rb8}uQ-e^$%4ac_Mo~i$_`l6qXGoEI=MItv( z#A>8nNr5%mJEEiKldlurQ(Ln!$+R(v?rpavL6}iGz%M01^dt(dB*A46QGhqpsoolq zTj^ru?zM`0tzI0H(1^1({aITwQ8g#qhfTJ>#Aue!T(G}RG|Rgg;@u1{8g(F#3l9AS zM>5g*mN|zlbH2o=c+VVnzK$v$8tl~Z#X9f9_+p*+|7Za`|5x^5obKX9F=1aimT(G?*eB`i`s@dCq}L! zjQk1S;>Eu2F7|zZNA;JD{DZysi*-L$i#Zu*JjrZ_MlQgOq=jNw7c&g4tDeZyt}!J0 z;By3Fj*x{j0Hm!E2G8$Qf?<`2J6(^kb9tNv{ua?==JoXCxIk#=WI z_-^gSZ~pz4>g!#sHx}!uAtG`yW2}oV^xfDP`M0{aw>UgvarnCkemyk#e>ZHN_$xK8 z!)i%bEzu=TwKSkwqEq1-iCZH@XD>86s(a^bx$xq#<-&}sm5U}C{3xkIB63G$q)IR( zS_Aghz#vox@I6;Q3dj5Iip0u;=xt}}ZD%wLHGq3g(>+8_*;9Ug=B;0G&{vBR z4T~6Wq>7x(iW~_ugAS;333V>|X>W50Z4BsfDLpP4PO!}*w0UTf$v)>~A6<^_l!ToU zl{hY?AD5zERw(wWlzx=~PXfY|08O$oD69-pa5U&T8dONtcZQrhL$ui9aC&j<`~AtR zAs_tN`kUXVW9FQ}c+QYa(r(U7-JBV9c7zH&&Q5upO$TrL;MDfPQHjFkJ3soy<-5OC z2k-GX<8fvxh}@eJ$sRJ(E>ywdhDWaSR3~dTXa$TJHfY7aHdjsh^5Tzvuhw~Is(B|9 zi1qW-_|H?LA1Ns2p%VX4pWWRXru>Vh;Ee)TV*?@1+YpntWxag zG=g`Js_l;4HyZ2BIihKfm`1``&lOE`88A6un;gi(H6Gqis@ev%D@n24Y9l1eE{P}2 ztdoY+NkIZy{O%Tim~28^^#`u{pC+I}9$X;@u)UD!IyrxxED^9<_U@L4VRJ!ZwUV(q z<_-#=S_xMx&tv-mY*2g~l;H@ToBQk8@g1-INgZ!b;*3w?sP#5-bxf>>v^!;RykTTO z^|@J?sN~(E;8|Knw3`;q%dJ1(ICg(=YIL1UEnE zUfP#2%?e8cw$xkmz4c4DQfWriK9~XQ3fmW&!l->j=GGaM}Ybg zN3N!g)CgW;?ao@PVd-nXTXd)9qyJK$-K{v|EoNoSs^8EUR{h?%{%;#wUhnEldoRv- zkMRSKT$L81e~W-}St^&G!*T!Mhb8-MdyIcpU5F3F84tuoC*1xcj#}K;e)1Q!M_S{I zt<1`v)sLV@Sp5j2SgN^dSNP&+{>15gZ}bnw{*T(;1DQz&nE8C1J9C^n`t%^zb?)qS z%+AbeuVuBDstH@$GwA>6*i7F+fN{OmAxR0%V&+lv321gzR%x{ytX_LI! zsNCTl!UMt1#d<2kRIGU_6IV0MQyJe;kkCAp@s$Lic`B1b&YGt(F7-e{^Hj!_9RST! znYfo~p31~YQS(&B2_KSZp31~)QS($L9*3HzGVv$WJeAQ)0rJp1l~G>-pm{12_d(55 z85c<)p?NB!mjVFIQyEt}pP&S4KI&x3Mtp?Hl#Td^o6f4wM&yR<*h@ApWEvOB zLk9?W>t=^cvqO2fa|7Vh5cg?l5N<;Qs0?wHG4MiG$v;AgJ^#?dUwZTNdSf9;0o=*A z-pS8J<2is?1=d*wx#;HsU`K&%M}df^rqMM`Z58Xq05jAtM77(y zv9CalsFO`~ayG6eCf(a}_02c#j#oEO#-^Farlq2-94AKAygn^nRjAMz7p5Inp^9F( z0uNlf)th`PY9vdIluW(^8zjpH25gip8yT=kvTS0&X34Ud0kx8)mH}HN%N7P~l`LBs zaLDgHHyne4?6&=1o7+`Q%B7ZcT8s8a^xDFb!9*q3SDmzjmV z`lF`FHyaDiPE-S0u8lLUW!CeouRfHk`RZd@0Gh8p0h+Hq15{sq0yJNJ0yJNJ0yJNJ z0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ0yJNJ2B^OJ3{ZXb z3D8{g3D8{g8KAo66QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~ z6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~6QH@~Bf#&0Izip#ba%O^cN5Gy z^A+9vn7h+EYvM9Se&^7nx8VJ#Pc_%aPAbCa-)GbBvso}#04+9s3jLw|_@mmT`c47lRZUtz#Fr+yrB#CnA@bww-$7}WHXGyRm4R(-ah zpUs?xUMv_E3$*H$g1(ZG*en<}GZK$;^^bFD)pv{f-692_i~7$Q@P(-Vf&r&R{b>fA z5%p&n&?xE~8E{$DUuM7+QGbO2S4I6*2HX?%_ZU#;($~3YO?SKWyBW~t(zh|7-KB45 zK$pwV<)S^->oW8*pgCXPoKH)6lCOWlfGq`vEd?}>tp$dy3>fRyk9E^L#<}(57_iQ* zU&ny;ZvA=&w0ZPx9-2p|N8ibSE{~zhL+erF)z^4wlF2^(WajAm37`H115Wz%CmC?c zr$5DjHlMl87d>(e!*Y$JU&9=1Un}X?GGM2q-^qXte!~Vot!b@aU+bqe-R{?KXTU|j z;UbgdhTm|50XO}In+&++H{4>t3fZtiro}eN`X=VY{W)2GjseZGzL^1cW&K?S+>`b9 z7;s0iow%Pcxp3TSj%&3naHwRd2TBMPV%Oc zOp?ihX|g~w{X{T*!hlZ&?o%c@?FxZg!GJx2X%CZRkz`sV(M;9ln7v~(KVa=X>E-I|3pgr~jRYTC_!16J;UHM)!diJQFPCKC?xdWPwG z2F;^8!_>`yM;Y9s44OxaU}zEOQs7u#;xQ)1*lERZT8T>JneLDDZT`*F_n{5D`z7w~ zN1xEtgdtNCXe5N%1hF=Onho|R4Bnp*B(1!VF!;j1fj2HByh9{<68t?0qX?K}7&6H~ zODQ#sC}kpJo?7CBDRfU)Cl57XjxJbI&KzAP(mfUpk-o zA4H-j(cP0clz?<+TPA)OORbqahNX$16n{TGx^|j{wTC<->)S6$aHPb$= zHy75Msp09AdDJN;{6?jvsM11>)9n^-yM=aPyX9x?mOqeEKIFU~a#Uh7H()bIH^ytZ z0ksU+&t>lCXtdA+T;>4=G;?AzM~C)#PCUe{nv;&PX@WRS zpqb7T3T6s2$zy?FSs+ABW$@${2;Kz(ZCt%jP|qYeCk#0!(D$WH@V5!0NTv&Y`3rsY zxh(P7miTB8xh1|AmiWF+l2rN9t9*3+TIU^O>NePKfQ2L-VI81E$4J`SpQkrzx+K zJZ34*SqioJ%vW6V6*^AmD=*Jiq9%OEqg+WVSF%VR}Be) zR~flip_9@Hg+CFCrV6E8Q1}ZBxUZz$XNtYAjJ~hDN$RmQEG&&#p}lW@@8Xo7FPNs9 zKBjuOsUGTE(B>7}yhDi}`od7w!cY*mE77e@H87kMbtlE>G6KHq55$58Vi;ZTa1UDD zP-LCAK(%L`w_rn2bKb%YWbul3THVxFW~#@7j-;56#O`u~V&?GrIlLLS+5t=w^pgZL zZlnXa?=#%@^+)G%^ut&8YnOX<%e~Qg0Yty>n!fP5&@&&vF|X;E*M+_^0IqnsE8YU^ z5dd9Yt}6y`)uQ&u(x7f>FsjXf=>8D5KjcDZ;G!=FXDq$cHB;Rtz0l8epWnC zu}Df2p8wv|?`WnVz*ABd!ekarPiMj~9 zk#4-f#N%4vFfCvLeNS*&Cpfc6W)qx66P$E1pXjtsWF#g!-4mU(nQNT=*Epj?1Dd(f z$!&D{2m0HQ`0&>W*%UF}cNPBcc;?6DQ_!%oRnscXJEy ziU=tGhMaUmPDK|T0OJEm;{&PaQvhH>K$sB7C16S*X$m7TB`|nOz)K{m1Ig6^8~Xl2 z9{U2x`xtN_Fz`U2kVqT}Bp+cUjs&uf1oDVPLm;_K*`k1BPA(j0>8_1yfLu0Ezj*LGyzi0?LBPWkDMO3xb&of-V9U2a^{w5{rYQ z76)G-5^ICzwZRm8iJ_ETLG!ME1NpmxMMUC6aL|dMhk%p8djzd7gkXO41o6 z6;JF#re~DAGs+MGnv{$tC5r(?O^Tm@b4va>rHFtwrC*z3Mz1r-v{!kyS9uAe?&4Ic z`rPdD>UMdf#}{BeTRHz|tD%i?2z+8qeZpvqwKj7t;~`jY6YFjCGr!-K zw%MdZxI8wzPu`7+2sQS3p-;weI9vxUeMh19@zfY+GYB{OhvjaVz}e%GB@u zu{h%~#+kD+en4eBwTjHJvGdYCf78UFG7pQ~g4;dUdVd=#m*mz(cR;p|=P>B6u!b6|D~GszcGQ z8i-yAWnT#m!<)$Qzj_!f>{)6UEbLioSYK#osqu<(0NPn^#)M4>0K2-^vn zeJSa_jP1EV+H;cqoK%1|bpVqT(rRma3RxpA(MWzI=v~KUTQR` z2&Ggo;WL(pEz85vhxYa5>e2Q8aDApm{MeRZ+?GK<_}4S!>lq_SMY=O`yEEume3UWb zQ3lnFTZFt8M)A8LI&X+{{dz1f?^s^6i=cG}0=5GI`ud(xtY?_za-(8xi~;ZLGYv=1 z|Ku~Z?{3B!Z!-5ewp)aD3(ft$qPrjScXWGxGi%GHF`ub#%QC%TnVv2uPFTYytgm8o zPyF^@FW&BNs!)68L6ZGJQZ_z3u$2EFPskmTWrt)V=I>_7-7JN01VS_S`2BnQ!--*f zkN@X;{2vgoS@vv}6*SpHlFjl@Hp{;uV1?pYp(r>4L1KmSlNHKu5d88tc`y95(o>b10zGDj8`2aVY2Fz_+yT=V-DJwjn2G{PKo5PQL=4}O{kFR;lS|2ftQKIkwC$b099-k1_v#S-5Uc* zP6r2_X27MOd?^^!4*g%2d2-60SuzwHBU+75wHi6h9R@_Nk?%F;VRTc~Bge!~HswX9 zKy~kmUt-F`(?1|F-^|Z9=i##hu+ltarCE8a?%nkdo2T9F10zYKgWL)@y#A(@aTF%FXubPK!xGtv?&?`H7V^=PL#MppgY#<%ixd1*2I6eyG z;|dhOynta|Af1520rTMiPr%uL<817fB1m!}V7?IGanTCkdLa3FU;r*!0dxf{T>%FH z_XCdmf#|_GkSGosii7F6-~=!)Xr34338)O(D`SgAkXRiwtPZB*+7Q6;p!s-^C!i&m z+!7psi$#$5DyaV|m`uR6VDh!#0Q6J>iN`_v5lYzTU@o!U}CSaeU-=`$w zN*F4(PjT&2d<4`h`g%s9UUAhk5+{_ACzMy|;8%v8QbrTds&K7}le`TV75zmenSfi0 z=aw=Ym&Q zgPHSp`Umaw2Z+QjKex;8AmEHY^^8B0fJXldjs9;FaMoXRmhm;e;7`54B)R3!xaH3y zNqYQT4^zqlnOh(`&<6_IvPdpiB+CR;%I->eC;`jmq08kL30NU#tdR2vsFI(pl3zy< zNcrK|eT65p8j7J8RYT)%i}A}k8D^<(kC)35wu=c+9L zwDn_>zGBZWR{r250B!wqmUb0JQZZ9=HaetshDBJ#GDnp9zrA){i(10MOQt z2=Ikfmp&^b-3lqX4-F$_pQPU>*{}}*9Fz3NBpdc2fEG#LBH6GH0bG;x*CZSE;jsq=tI8~906SGJo%_D!Lv>I%7T4_UW|^6?{wr)*Q<@$R+i_FAJ34loif z+FTcHK0J2|pv~rLV?evj)y{xRHrFKvl-bQ?v1>mek4igVX?Nk-T>#7M{4xftu=6Vz zu*%M_VnDT>uV%nnJHM6z8|?fB25hqPn;1}Q=W7|T!_Mzuz%Dz#ivhds{B8!+I5TRT z1Mv*;)9=3e&iEwDYW3-s3^11rh)$}|?Q;f*a|X~g_`J-#d6`ig-^y!0eK7CF*VU@m z!aN5z&kA>v_X0NsxCZbudYJ&yDq2J|}8dl_)sk$#&2 ztDKHi&OB_)9|!C{{m%;18dYx|qc@IW&V$yQ)9TI9O%-U}2}O58q1!(5!iIU_EF8>B zlRtC)bg2wO9?m7k8Lu*Dt=O|ZkQsZ{r*-HrhAm%qu`g{`oUw~Jufx6wkd}QB223#u zJy(9TR<*m-r||VDQH>7zYzFU`!MiXZF@UjxW31r9=(YgP__EIU24PsA;V0{Yrp5mP zU*>CZm#v1&%)ZwRyY+@WnmbhF0zc#eAKj1lo~s`c_s0`!)jhN2amM9Lgbg-wAEebH z_hIJ&&?5H{phfN@K#SZ*fEKwA!86CNCEXLhxn7OR@^!NL>ty<@CZzP6kP?0Gp}3Dy z`hCQJ2lmVd_NW~IBs!g$olXiSWeu2=MW>mMbNYRpLo?mu((iHg#|l9n^WE|D-BEiR zfCi7g!9yi3dG(jP6jb`-D;d|H2|@jYAeE>I52y)8>+#2dn>;4@>iWLTpB(droakEx z#c3Dtk_u_J*b<;!T8m)mvKu8?Z=J0f27S0L&bW>_UCpYvkh`YhVp;&2ic5f|;u4^# zxCCe_E(25*mjF%0B|uYg3D8tr0yGtu08PauKvQuE&{SLkG!>TsO~oZZQ*jBTsO~oZZ zQ*jBTsO~qw^s^Su$skjVKRa^oz6_)|3ic5f|;u3JGPsJrbQ*jB=y02MOw-iBL9U*eE?32 z_S1~S8IeE3NL&`}ml=sGB7cRE*x-9-gYP|jQqbB)Us|J&e(A3HxNAOIu^wMikB>HP zio{KcsW_0w6v;V7$|9Mrl^km&+S-3fY5$UFDMzKWqYP+}QW~VF9|dIEB&9Vm63x=E zW@!{DrA5kbk?0ERvXpk2NpeL>xx!>xaJ|R%Shal`Fo7S%z%AnfEGI|z|RWMJZ^@5a5MBSY3&MS_zGq= z7!$US3DZ2LhOJX$4j<5kE5n&9!_fuT8-IN?;(PAwjjG18v|r-Ve$=kFu3!GTe#5ai z+^kUbDArmCdK7Cd48FCje9-&f+Ss?^bD1x@sAJdcyys`wTQ=P-TXg>$W{&H2%XPbe;fesX@Rk-{h@SVK|Mu#QGk4djRXmsl7vaIb6?;1@4qH4d33Q>HJUAnGcJjR2Cz3i%B!E7xFrFq_T^JaUp_^(k)3f# zJDD};6n)Z^*o9P(w+$_F8z%?=FnplKe8z6B_9Cl3{O+bg3)4@n| zh{HMlv`Y zW#8ZuiNr>ieIo-VxU(m?3o#}wwi z8Suzsf8-JI$UI0q_ShdY;EA05BzBDjNIa2;K9OHUGXsFqKu&3(2zQ72z_8N5XaeR0 za^^4+a{|NWFcO~xvOkGA+&~_41KD#K@KwO|Rltje2$1+HF!HOwD+F8&xGpjh7Xu?N zG7{Z^laXVX0qGb0fg$Qc0Ui$R? zi_dLU->rR58TUOEef?3Q@8wpkw_>OIAd>}Q^MY^&?(hLv95yVDT_Yi_E~?+XZ^GA6~aq9)LprC8Qi*zs6i0Q zyPlDIJ);oczdkVPddABHbY~QGXM}KP5hS`Zp6oK;6)8_OmNQ2a zPur4CGae>qY$<1KbiO_BNIK70C|evUEe z9j@FR3}|(Utu7DAhZaIz~}C$<_q=s+&%1bH}y6+;LbhZrhQuC8C>ECk~~U0FP3;{ z<4Qd5mv}xP5>q?{Q#{dKD=1~Er)a7tOeD^DTxXcrRgE68(L*29S&w*@0rx$^eGl!i z`<~(VJ@jltv3EeRmo~20JEGY8GAVYk*R|M7`(v^9`NiHhh{SU5z~x?vfEC_>D;TiL zTd>PZ8&~HYRp*WRI6!L;dGii=smgKKJN&Sh*7UA-;9Vxud){I9y!0`Q@dd{CqIPn~ zwAlATG2>-F-j_YzN5{tmUtj{8#4i;4X^)NdJIDIzz#Zq$8Rw^tI^+F0;~7xxlO zUz0Wdj5Q26;m}1L?2eh>RTc6VMqP)ET7C2v33opD_2}jDg6=A^L1T3Jv;*am=3)8Z?6e^FrQvq3Eg* z9>bc@@HHX&C^v;dn?g}@14wKRy|6h%Yq~8I+!lHkYx>XRcmI~vF6~q|**>xxKC)Bq zAZ;HUMIke7ADjSfADjSfADjSfADjSfADjSfADjSfADjSfADjSfADjSfADjSfADjSf z9~?pMG-dhdwNrPhAH%&i-QL)lbNH}awHvP5tz@NJ$?Gfm6f_XJzq5FB!fod+b%otz zFg7s}fZr0cA?tnMGX!`!> zIWX=-=!7`hO^Nx^h77CZ9Os&^lyOilhG z16tLXVr+~ZGk`{DcZ%Ud1EAe0Mu2vw7y;UyVgzV+iV>jQDMo;Hrx*d+oni!NcZw09 z-6=+ZcBdEt+MQwqXm^Scpxr4(fOe-C0ot8n1Za1P5un{EMu2vw7y;UyVgzV+iV>jQ zDMo;Hrx*d+oni!NcZwlMIdI{(2Tu>!qxyo4PwGED$%!Fn;E8SGt(*89w3Gwb%olFv z6#{DcLA88>LIL4GzcI<^Yl)Dt1oiBJ~05&>uqnVLgc=t>0O zQpj{El!dND0B(l3n;{WhiQe$mtUs30y-y99{He$AsfTuicKH<+2OV+Dt6qLZz$34E z`4s`^7XS`dT-uAb>2T0XFL}deMHb_`xAuy33LrLQxImxJc%(zxN1yX?A0xf z`6@s$8@#3s-Yh(t1z?-ZZIeZG9sqDLn0zsqhdyn2mCwzcx$ONzeK%O88YiSux8xR= zuEmvvjewdR^y&_JDY)a+-C@8OSvN+e5|u$+Wsri$LEU2ptPbf`hoTbb?xXHGv5~;x ziLjBt|J3;CXK#JE>9E>~v-%~@>KAo~DZ0|cywb#| z>Hx)5`E*sj=%NXp^Ddd&CFf!%y0719I{oUMqpIc%Pw-%DudDgnZ@OMw@#tvZvpM_HAKKg5C@k++>!d zRf?}l8HUBy{Hmxe?M^wFTfX<};ok=$0>9bw3Y?so}O$mzWkSrZiI(irZ*eP>6Wh;&%0P7TPonl3&9NhI# z-|L$h+|7*WMk&0tmju%#!G>;g0NNC;O>v-4B!G5>Ymb4)|2%Zae81{sf2it}q`WIh zLr7J3`ZIR=vxrmDS^t2uevyFl{(i?2XrtJ9e%mPKa@yx`!l+kOh1!zKa&g4 zu@IVDDQ8y7E&^7{c`M~13>dyrewKhL*d|HFfQ#Kb=xXMtO>{JcTY7=GqhI z+M}~JhAdX6{+VLh%-EfM@MKy=u2syyE#zsx``wFUR)kKg*IHiB&%e%GYdNJLe@g5Y zOOW`mApgS{xHY)Zee>+DG3+T0y!=5era3RIIWIcC zA=j#c!m0v4o^R*_&sP<^is0b#FVFrg@ov);bLbt4*JPM* za}q#DMsi1n9d{l9+{{S0nPI_K4#1s^}m4H=F z!zw37K#eoG#%U*@!Rcsl=AdQ?c^r58k2{AFaMGzi=`<10=rlAsIRY*^%@>`i1RN0b z2Luz|Y@lW;n2!so1Z>So+M1Jwr}98zTTarpoHPtp4xlTie^-tTk5}{op({ruU_x%{ zgxm}QCg!G2WI#`DLQk#*Ph>zIy}7Bqxfu+|>dhTUz{6bU!`$3=;^*(W!*!|G7(C3C z9_EG#m>}j(5C`FDL6Dd!X3Z1_;*8V>3TKLb0uGCY!y-rSYCIwuj{FzHo7PJrv||&1Jae z;_!MXwWcmZ2Lq1exsT)p(7_p8u>a4EkaG)uGPmGYIHRd~6eV{Q*-ytUdL=y=Y6Z|) zl-^n7WI#@5Q2_(ookalzP>SX}h5+*Tx=8BU|rhWu9R;r&jr z_iX~kdG+JGCIS|For}G>sJlUu#oo6Ud*35qwO3f}6;XAA=4*?61dwEdcfB~p6v@dlO_4(YN$v)R-3>mA z04kH**6yTe!E?;N3bu-*il?Rkx z_FczGQ5D_p(rtG|^<-GB&hl}ye7X6Nt5Y%3xw?g1EbEG8x*sxCHcgcW;9&wtTcMaL zlmQs3pnPw?-!FUZAI<7w@#{F_*KyHepq;g5q1H?nse6^wy|Ey1_&%tLRIy)I9CJp3 zV$S)^=VHgM09^8?UGfjaR|deRvf)%JE4={k#|6#>!7?)1s-0-QN2py0emI`4@(0f9t|M`-a-!|fGM(G^d0UjQUJJ?Whus<2=8 zyma037PbK-x;?&b&nNFz--w%Tc-W7K+aqs7ZQm@^6*76bz!cQ zM^wtREz9NH<#KdW5h}J@9=Thl^=J>e+k@0}w_3?xtxyADwKBSziDk7x8MQ(ACe{NN zSnA-~CF*uDVV@p~_J?8tdi>xjNfm7ub?stwYYB$VeX-!aI21+u9a{18;UDh3sBTkS zQ(qveXt7ZkpjB)X1^`+V2CO0gEeZpIhs~kC_1ji|QO)>lobfD^dwIg3@`Rx%3Tej( z1I7qZTYt;+E5Fn&`eBxvLDA-jAE!k4j8U;HD-} z(38ORFrd`Hl^UqTLj(7a0i}stX(E-VOXTVpaL~vdG*XFPBiGA-$tG^HiApRnaZ4C5 z-^|T7Q;C&kZY2Y1&0H-5PMNt=45+kll@^-ECXU<0QBcou^$fUaOTNi?^G>oSOtRC1 z3a$3!R(o{1SD%Wt2MAkpAVZeS#Kipe`gn#Tz-`2+*n#N;*x+!m8> zGhmx5d7F#o(c-eSxabD%7;nND<`l*vZ^9x59Q0ZadTEl=a`I`JX4)$!_cGw4K=MZc zDseWDe3k+C1IhOpP#LsT25BiR!Gsp(@Wop*8vqb>|!Y(N+r7=kNA0P_OEyuc7#hygep$UYqKAi$1LJD$C` z4LY8^xb4fo4Xk*+bpxCUgLPJ%@opU5$z~T%(7fx+;%Tt`kH6V3#dWA#Q{C|?-LWa_ zf4bg#;hzf}9cr#+`ouDQbO8jJtkZMr^ig*?01tDMALd4<>Vf}R-h21f_d5CvkREP| zC!5Tw=RD4Ho+6^MRCyg$-a?`oT=a?;y#b661i4T2r%m+d;nGMg-)~;$AAsu~09`?6 zSFi}zmjD)ovKEAV7`ebwI>S9~;rkuxiGy(kS>pZsl8FzjXQ z0$q;6E=QQuz1vaH?V#Vt9!EhB19}|=y$rbRD7ejlRn9@HoY7T9<-FDh-~N5eb@c&k zi!*GC-8uE{-r2KFAHM;|^+217Gp=TiN|h#Xr3rK!@sp&4Pm-b&UCW2(lKGn2zO=9m zZD!8MvKLvaeI@7?2xe^NkJnH6^y4vYeQD>V8t0|bhqu$K+sVvQ(?h!Hq3DEy2cA{S z6nnlAMlE~35#A^5d?W6{et+uk{`SW|PP?V{#3ifYQtW7;Y3DD>=1zUw_m?Gl!;;v@ z%bx$rrcvg8-D)2dC-pB*;)$hy18>^Er{RkP?L5zO=VKSb)&6jQ$?xBOuUjqeY`XDm zESli3uL?6q`ww)R;j}(`GaM)`+RboyTpNIPGaMe*2B6&xM}T%S90A(RaCo2_l4v)> zp}7Tsb~79S+RbnTXg9-Q=Rp$fW;g=0o8b^(_tt)MOkeKh9z%I7;v|S_F@#VQfEGiD04;_P0a^?p1op*$ z*8k$uguAL~?yD5zSB&eAc5yX|Lhjnd)dXl4S0liU{7jusR=pQB+a((nJtt zFHO9${CdL9Dc`=Y8ep#G=U>eal8c`@^7A_wFs&eeT0sz}y1#zot@Xd+e{o+O@DCC) zA0$MbK5!pP9UE0{U6nh!E`wtBx($2XS-6q)xBRme2WG$fK+U8#)!3UFRb_FTN}cpe zJY0z<3%de3tI6YR@)VLuf2G&4(mRAq`d@j)ue>su^e6b!Ciq=s(qHR0uk{OL(!U;b zUJn+MNxv+VRTlE%q>n>V?biK_TLN?|>y{wyoN*`dZ#N#O^GHLSv4L@EsEZfs;^{ZF zoEOUZD8S;>&YKs~&5K1YhR$0bGOZ7}F<#E4uj5}Ze0u7kY64pjXI#Kc>HFgn_Q!S# z(5FS6Ntc)gE-{V3-h|3Gg>+4^Nf&0)8pTwjY1a=4Zeg1UsU}ue9+kTalc`G#qF{gcG;qq zMJP@Se}HudpoKpmKns6BfENCM04+iQ0?YsVTdQ^RUmmM|3KdDlir9tdkb6~*t|})H zMFFh#>Q^&P9-Ur8rwi$GP{Mm2cQ``KfKfeE`@#-&E>U3{JjjW q6Ic8G*ABmb1wIHD<8= sample_interval) { - uint64_t db = bytes - last_bytes; - - if (db > 0 && dt > 0.0001) { - displayed_speed = (double)db / (1024.0 * 1024.0) / dt; - } - + uint64_t db = (bytes > last_bytes) ? bytes - last_bytes : 0; + displayed_speed = (double)db / (1024.0 * 1024.0) / dt; last_bytes = bytes; last_time = t; } + printf("\r"); + if (!scan_done) { - printf("\rScanning: %llu files | Hashed: %llu | %.2f MB/s ", + printf("\033[1mScanning:\033[0m %llu files | Hashed: %llu | \033[32m%.2f " + "MB/s\033[0m ", (unsigned long long)found, (unsigned long long)hashed, displayed_speed); } else { @@ -849,18 +844,17 @@ static THREAD_RETURN progress_thread(void *arg) { int barw = 40; int filled = (int)(pct * barw); - char bar[64]; - int p = 0; - - bar[p++] = '['; + printf("["); + // Print filled part in Green (\033[32m) + printf("\033[32m"); for (int i = 0; i < filled; i++) - bar[p++] = '#'; + putchar('#'); + // Reset color for empty part + printf("\033[0m"); for (int i = filled; i < barw; i++) - bar[p++] = '.'; - bar[p++] = ']'; - bar[p] = 0; + putchar('.'); - printf("\r%s %6.2f%% (%llu / %llu) %.2f MB/s ", bar, pct * 100.0, + printf("] %6.2f%% (%llu/%llu) \033[32m%.2f MB/s\033[0m ", pct * 100.0, (unsigned long long)hashed, (unsigned long long)found, displayed_speed); } @@ -869,23 +863,23 @@ static THREAD_RETURN progress_thread(void *arg) { if (scan_done && hashed == found) break; - sleep_ms(100); } - printf("\n"); + // Restore cursor (\033[?25h) and move to next line + printf("\033[?25h\n"); return THREAD_RETURN_VALUE; } // ======================== Hash worker IO Ring ======================== // -------------------------- Configuration --------------------------- -#define IORING_READ_BLOCK (4096 * 64) #define NUM_BUFFERS_PER_THREAD 16 #define SUBMIT_TIMEOUT_MS 30000 #define USERDATA_REGISTER 1 - -// Global stats +#define IORING_READ_BLOCK (KiB(1024)) +// Globals +u64 g_ioring_read_block = 4096 * 64; static atomic_uint_fast64_t g_io_ring_fallbacks = 0; // -------------------------- Buffer structure --------------------------- @@ -964,28 +958,34 @@ static ThreadIoContext *io_ring_init_thread(void) { } // Initialize buffer pool - for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD]; - // 4096 alignment - void *ptr = _aligned_malloc(IORING_READ_BLOCK, 4096); - if (!ptr) { + u64 buf_pool_size = g_ioring_read_block * NUM_BUFFERS_PER_THREAD; + + // Reserve and Commit the entire memory chunk + 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); return NULL; } + } else { + return NULL; + } - g_thread_ctx->buffers[i].data = ptr; + for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { + + g_thread_ctx->buffers[i].data = (u8 *)base_ptr + (i * g_ioring_read_block); g_thread_ctx->buffer_pool[i] = i; g_thread_ctx->buffers[i].buffer_id = i; - } - g_thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; - IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD]; - - for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { buf_info[i].Address = g_thread_ctx->buffers[i].data; - buf_info[i].Length = IORING_READ_BLOCK; + buf_info[i].Length = (ULONG)g_ioring_read_block; } + g_thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; + HRESULT hb = BuildIoRingRegisterBuffers( g_thread_ctx->ring, NUM_BUFFERS_PER_THREAD, buf_info, USERDATA_REGISTER); @@ -1010,9 +1010,7 @@ static void io_ring_cleanup_thread(void) { CloseHandle(g_thread_ctx->completion_event); if (g_thread_ctx->ring) CloseIoRing(g_thread_ctx->ring); - for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { - _aligned_free(g_thread_ctx->buffers[i].data); - } + plat_mem_release(g_thread_ctx->buffers[0].data, 0); free(g_thread_ctx); g_thread_ctx = NULL; } @@ -1183,11 +1181,11 @@ static int submit_pending_reads(ThreadIoContext *ctx, size_t bytes_to_read; - if (remaining >= IORING_READ_BLOCK) { - bytes_to_read = IORING_READ_BLOCK; + if (remaining >= g_ioring_read_block) { + bytes_to_read = g_ioring_read_block; } else { // Round UP to sector size (4096) - bytes_to_read = (remaining + 4095) & ~4095; + bytes_to_read = ALIGN_UP_POW2(remaining, g_pagesize); } HRESULT hr = submit_read(ctx, file_ctx, buf, current_offset, bytes_to_read); @@ -1302,7 +1300,7 @@ static void xxh3_hash_file_parallel(ThreadIoContext *ctx, const char *path, // -------------------------- Hash worker I/O Ring --------------------------- static THREAD_RETURN hash_worker_io_ring(void *arg) { WorkerContext *ctx = (WorkerContext *)arg; - unsigned char *temp_buffer = (unsigned char *)malloc(IORING_READ_BLOCK); + unsigned char *temp_buffer = (unsigned char *)malloc(READ_BLOCK); char hash[HASH_STRLEN]; if (!temp_buffer) diff --git a/xxh_x86dispatch.c b/xxh_x86dispatch.c index ddb71e1..0c15820 100644 --- a/xxh_x86dispatch.c +++ b/xxh_x86dispatch.c @@ -1,5 +1,3 @@ -#define XXH_INLINE_ALL - /* * xxHash - Extremely Fast Hash algorithm * Copyright (C) 2020-2021 Yann Collet From 0294498538a33933b74eecb4add3abefaa5f314a Mon Sep 17 00:00:00 2001 From: amir Date: Thu, 2 Apr 2026 12:08:47 +0100 Subject: [PATCH 3/7] Add support for multiple inflight files and one shot hash small files The IO Ring now 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 --- binaries/changelog.txt | 4 +- file_hasher.c | 3 +- platform.c | 598 ++++++++++++++++++++++++----------------- 3 files changed, 354 insertions(+), 251 deletions(-) diff --git a/binaries/changelog.txt b/binaries/changelog.txt index 1be6243..0db7f6c 100644 --- a/binaries/changelog.txt +++ b/binaries/changelog.txt @@ -50,6 +50,8 @@ Fixing user prompt parsing Reorganising the code Improving the scan function -5.0: Implementing the IO Ring instead of buffered hashing, huge performance gains. +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. +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/file_hasher.c b/file_hasher.c index 772559d..9bbe3d2 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 - u64 g_ioring_read_block = ALIGN_UP_POW2(IORING_READ_BLOCK, g_pagesize); + g_ioring_buffer_size = ALIGN_UP_POW2(IORING_BUFFER_SIZE, g_pagesize); // ------------------------------- // Scanning and hashing // ------------------------------- @@ -120,6 +120,7 @@ int main(int argc, char **argv) { // Starting hash threads size_t num_hash_threads = num_threads; + // size_t num_hash_threads = 1; WorkerContext workers[num_hash_threads]; Thread *hash_threads = diff --git a/platform.c b/platform.c index 0d611e7..c9cce03 100644 --- a/platform.c +++ b/platform.c @@ -874,14 +874,48 @@ 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 SUBMIT_TIMEOUT_MS 30000 #define USERDATA_REGISTER 1 -#define IORING_READ_BLOCK (KiB(1024)) + // Globals -u64 g_ioring_read_block = 4096 * 64; +u64 g_ioring_buffer_size = 4096 * 64; static atomic_uint_fast64_t g_io_ring_fallbacks = 0; +// -------------------------- File context --------------------------- +typedef struct IoBuffer IoBuffer; + +typedef struct FileReadContext { + HANDLE hFile; + uint64_t file_size; + bool 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) + }; + FileEntry *fe; + + // Completion tracking + int reads_submitted; + int reads_completed; + + int active_reads; + int failed_reads; + + int reads_hashed; + uint64_t bytes_hashed; + + // For in-order hashing + uint64_t next_hash_offset; + uint64_t next_read_offset; + + IoBuffer *head; + IoBuffer *tail; + +} FileReadContext; + // -------------------------- Buffer structure --------------------------- typedef struct IoBuffer { void *data; @@ -891,44 +925,55 @@ typedef struct IoBuffer { HRESULT result; int buffer_id; int completed; + + FileReadContext *file; + + struct IoBuffer *next; } IoBuffer; -// ============================================================================ // Thread-local I/O Ring context -// ============================================================================ typedef struct ThreadIoContext { HIORING ring; HANDLE completion_event; IoBuffer buffers[NUM_BUFFERS_PER_THREAD]; int buffer_pool[NUM_BUFFERS_PER_THREAD]; int free_count; + int num_submissions; + int active_files; int initialized; } ThreadIoContext; -// -------------------------- File context --------------------------- -typedef struct FileReadContext { - HANDLE hFile; - uint64_t file_size; - XXH3_state_t hash_state; - char path[MAX_PATH]; - - // Completion tracking - int reads_submitted; - int reads_completed; - int reads_hashed; - uint64_t bytes_hashed; - int failed_reads; - int active_reads; - int buffers_ready; // Count of buffers ready to hash - - // For in-order hashing - uint64_t next_hash_offset; // Next offset that needs to be hashed - uint64_t next_read_offset; // Next offset to submit for reading - -} FileReadContext; - static _Thread_local ThreadIoContext *g_thread_ctx = NULL; +typedef struct FileQueue { + FileReadContext files[MAX_ACTIVE_FILES]; + int head; + int tail; + int count; +} FileQueue; + +// ---------------------- FIFO queue operations --------------------------- +static FileReadContext *fq_push(FileQueue *fq) { + if (fq->count == MAX_ACTIVE_FILES) + return NULL; + + FileReadContext *f = &fq->files[fq->tail]; + fq->tail = (fq->tail + 1) % MAX_ACTIVE_FILES; + fq->count++; + return f; +} + +static FileReadContext *fq_peek(FileQueue *fq) { + if (fq->count == 0) + return NULL; + return &fq->files[fq->head]; +} + +static void fq_pop(FileQueue *fq) { + fq->head = (fq->head + 1) % MAX_ACTIVE_FILES; + fq->count--; +} + // ---------------------- Initialize thread context --------------------------- static ThreadIoContext *io_ring_init_thread(void) { if (g_thread_ctx && g_thread_ctx->initialized) { @@ -960,7 +1005,7 @@ static ThreadIoContext *io_ring_init_thread(void) { // Initialize buffer pool IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD]; - u64 buf_pool_size = g_ioring_read_block * NUM_BUFFERS_PER_THREAD; + u64 buf_pool_size = g_ioring_buffer_size * NUM_BUFFERS_PER_THREAD; // Reserve and Commit the entire memory chunk void *base_ptr = plat_mem_reserve(buf_pool_size); @@ -975,13 +1020,13 @@ static ThreadIoContext *io_ring_init_thread(void) { for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { - g_thread_ctx->buffers[i].data = (u8 *)base_ptr + (i * g_ioring_read_block); + 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_read_block; + buf_info[i].Length = (ULONG)g_ioring_buffer_size; } g_thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; @@ -997,6 +1042,9 @@ static ThreadIoContext *io_ring_init_thread(void) { // 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; @@ -1015,7 +1063,7 @@ static void io_ring_cleanup_thread(void) { g_thread_ctx = NULL; } -// ---------------------- Get a free buffer from pool ------------------------ +// ---------------------- Buffer get and return ------------------------ static IoBuffer *get_free_buffer(ThreadIoContext *ctx) { if (ctx->free_count == 0) { @@ -1043,6 +1091,7 @@ static HRESULT submit_read(ThreadIoContext *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 = @@ -1055,6 +1104,7 @@ static HRESULT submit_read(ThreadIoContext *ctx, FileReadContext *file_ctx, if (SUCCEEDED(hr)) { file_ctx->active_reads++; file_ctx->reads_submitted++; + ctx->num_submissions++; } else { buf->completed = 1; return_buffer(ctx, buf); @@ -1062,11 +1112,40 @@ static HRESULT submit_read(ThreadIoContext *ctx, FileReadContext *file_ctx, return hr; } +// ------------ Link completed buffers in an ordered list ------------- +static void insert_buffer_ordered(FileReadContext *file, IoBuffer *buf) { + buf->next = NULL; + + // empty list + if (!file->head) { + file->head = file->tail = buf; + return; + } + + // insert at head + if (buf->offset < file->head->offset) { + buf->next = file->head; + file->head = buf; + return; + } + + // find position + IoBuffer *cur = file->head; + + while (cur->next && cur->next->offset < buf->offset) { + cur = cur->next; + } + + buf->next = cur->next; + cur->next = buf; + + if (!buf->next) + file->tail = buf; +} + // -------------------------- Process completions --------------------------- -static int process_completions(ThreadIoContext *ctx, - FileReadContext *file_ctx) { +static void process_completions(ThreadIoContext *ctx, FileQueue *fq) { IORING_CQE cqe; - int processed = 0; while (PopIoRingCompletion(ctx->ring, &cqe) == S_OK) { @@ -1074,273 +1153,294 @@ static int process_completions(ThreadIoContext *ctx, continue; IoBuffer *buf = (IoBuffer *)cqe.UserData; + FileReadContext *file = buf->file; - if (buf && !buf->completed) { - buf->result = cqe.ResultCode; - buf->bytes_read = (DWORD)cqe.Information; - buf->completed = 1; + buf->result = cqe.ResultCode; + buf->bytes_read = (DWORD)cqe.Information; + buf->completed = 1; - if (SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) { - file_ctx->active_reads--; - file_ctx->reads_completed++; - file_ctx->buffers_ready++; - } else { - file_ctx->failed_reads++; - file_ctx->active_reads--; - file_ctx->reads_completed++; - } + file->active_reads--; + file->reads_completed++; + ctx->num_submissions--; - processed++; + if (SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) { + + buf->next = NULL; + + insert_buffer_ordered(file, buf); + + } else { + file->failed_reads++; + return_buffer(ctx, buf); } } - - return processed; } -// -------------------- Hash buffer in sequential order ----------------------- -static int hash_sequential_buffers(ThreadIoContext *ctx, - FileReadContext *file_ctx) { - int hashed = 0; +// -------------------- File operations ----------------------- +static int init_file(FileReadContext *f, FileEntry *fe) { + memset(f, 0, sizeof(*f)); - // Keep hashing while the next buffer in sequence is ready - while (file_ctx->next_hash_offset < file_ctx->file_size) { - // Find the buffer that contains next_hash_offset - IoBuffer *found_buf = NULL; + f->fe = fe; + f->file_size = fe->size_bytes; - for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) { - IoBuffer *buf = &ctx->buffers[i]; - if (buf->completed && buf->offset == file_ctx->next_hash_offset) { - found_buf = buf; - break; - } - } + f->hFile = CreateFileA( + fe->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); - if (!found_buf) - break; // Buffer not ready yet - - // Found the correct buffer in order - hash it - if (SUCCEEDED(found_buf->result) && found_buf->bytes_read > 0) { - XXH3_128bits_update(&file_ctx->hash_state, found_buf->data, - found_buf->bytes_read); - atomic_fetch_add(&g_bytes_processed, found_buf->bytes_read); - - // Update bytes_hashed for this file! - file_ctx->bytes_hashed += found_buf->bytes_read; - file_ctx->reads_hashed++; - file_ctx->buffers_ready--; - - // Mark as processed and return buffer to pool - found_buf->completed = 0; - return_buffer(ctx, found_buf); - - // Move to next offset - file_ctx->next_hash_offset += found_buf->size; - hashed++; - } else if (found_buf->bytes_read == 0 && SUCCEEDED(found_buf->result)) { - // Read operation failed with an error code - file_ctx->reads_hashed++; - file_ctx->buffers_ready--; - found_buf->completed = 0; - return_buffer(ctx, found_buf); - file_ctx->next_hash_offset += found_buf->size; - hashed++; - } else { - // Read failed - file_ctx->failed_reads++; - file_ctx->reads_hashed++; - file_ctx->buffers_ready--; - found_buf->completed = 0; - return_buffer(ctx, found_buf); - file_ctx->next_hash_offset += found_buf->size; - hashed++; - } + if (f->hFile == INVALID_HANDLE_VALUE) { + return 0; } - return hashed; + // Determine hash method based on file size + if (f->file_size > g_ioring_buffer_size) { + f->use_incremental_hash = true; + XXH3_128bits_reset(&f->hash_state); + } else { + f->use_incremental_hash = false; + } + return 1; +} + +static void finalize_file(FileReadContext *file, WorkerContext *ctx) { + + FileEntry *fe = file->fe; + + char hash[HASH_STRLEN]; + + if (file->failed_reads == 0 && file->bytes_hashed == file->file_size) { + if (file->use_incremental_hash) { + // Large file: digest the accumulated hash state + XXH128_hash_t h = XXH3_128bits_digest(&file->hash_state); + snprintf(hash, HASH_STRLEN, "%016llx%016llx", + (unsigned long long)h.high64, (unsigned long long)h.low64); + } else { + // Small file: hash already computed, stored directly in single_hash + snprintf(hash, HASH_STRLEN, "%016llx%016llx", + (unsigned long long)file->single_hash.high64, + (unsigned long long)file->single_hash.low64); + } + } 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); + } + + char created[32], modified[32]; + format_time(fe->created_time, created, sizeof(created)); + format_time(fe->modified_time, modified, sizeof(modified)); + + double size_kib = (double)fe->size_bytes / 1024.0; + + char stack_buf[1024]; + 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); + 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) { + + FileReadContext *file = fq_peek(fq); + if (!file) + return; + + while (file->head) { + + IoBuffer *buf = file->head; + + // Check ordering + if (buf->offset != file->bytes_hashed) + return; + + // Consume + 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; + + // 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); + } + + file->bytes_hashed += bytes_to_hash; + atomic_fetch_add(&g_bytes_processed, bytes_to_hash); + } + + return_buffer(ctx, buf); + } + + // Finalize + if (file->active_reads == 0 && file->bytes_hashed >= file->file_size) { + finalize_file(file, wctx); + CloseHandle(file->hFile); + fq_pop(fq); + ctx->active_files--; + } } // ------------- Submit pending reads - fill all free buffers ----------------- -static int submit_pending_reads(ThreadIoContext *ctx, - FileReadContext *file_ctx) { - int submitted = 0; +static void submit_pending_reads(ThreadIoContext *ctx, FileQueue *fq, + WorkerContext *worker_ctx, int *submitting) { - while (1) { - // Check if we have more data to read - uint64_t current_offset = file_ctx->next_read_offset; - int has_data = (current_offset < file_ctx->file_size); + MPMCQueue *file_queue = worker_ctx->file_queue; - if (!has_data) - break; + // Try to submit reads for the current head file + FileReadContext *f = fq_peek(fq); - // Get a free buffer - IoBuffer *buf = get_free_buffer(ctx); - if (!buf) - break; + for (;;) { - size_t remaining = file_ctx->file_size - current_offset; + if (f) { + while (f->next_read_offset < f->file_size) { - size_t bytes_to_read; + IoBuffer *buf = get_free_buffer(ctx); + if (!buf) + return; - if (remaining >= g_ioring_read_block) { - bytes_to_read = g_ioring_read_block; - } else { - // Round UP to sector size (4096) - bytes_to_read = ALIGN_UP_POW2(remaining, g_pagesize); - } + size_t remaining = f->file_size - f->next_read_offset; + size_t size; - HRESULT hr = submit_read(ctx, file_ctx, buf, current_offset, bytes_to_read); + // Check if this is the last read and the file size is not + // sector-aligned + BOOL is_last_read = (remaining <= g_ioring_buffer_size); - if (SUCCEEDED(hr)) { - submitted++; + if (remaining >= g_ioring_buffer_size) { + // Normal full read + size = g_ioring_buffer_size; + } else { + // Last read - handle partial sector + if (remaining % g_pagesize != 0) { + size = ALIGN_UP_POW2(remaining, g_pagesize); - file_ctx->next_read_offset += bytes_to_read; - } else { - return_buffer(ctx, buf); - break; - } - } + } else { + size = remaining; + } + } - return submitted; -} + HRESULT hr = submit_read(ctx, f, buf, f->next_read_offset, size); -// -------------------------- Wait for completions --------------------------- -static void wait_for_completions(ThreadIoContext *ctx, - FileReadContext *file_ctx) { - int has_active = (file_ctx->active_reads > 0); + if (FAILED(hr)) { + return_buffer(ctx, buf); + f->failed_reads++; + f->active_reads = 0; + f->reads_submitted = 0; + f->next_read_offset = f->file_size; + break; + } - if (has_active && ctx->completion_event) { - WaitForSingleObject(ctx->completion_event, SUBMIT_TIMEOUT_MS); - } -} + f->next_read_offset += size; -// ---------------------- Main parallel hashing function ---------------------- -static void xxh3_hash_file_parallel(ThreadIoContext *ctx, const char *path, - char *out_hex, unsigned char *temp_buffer) { - - // Validate I/O Ring - if (!ctx || !ctx->ring) { - xxh3_hash_file_stream(path, out_hex, temp_buffer); - return; - } - - HANDLE hFile = CreateFileA( - path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); - - if (hFile == INVALID_HANDLE_VALUE) { - xxh3_hash_file_stream(path, out_hex, temp_buffer); - return; - } - - LARGE_INTEGER file_size; - if (!GetFileSizeEx(hFile, &file_size)) { - xxh3_hash_file_stream(path, out_hex, temp_buffer); - CloseHandle(hFile); - return; - } - - FileReadContext file_ctx; - memset(&file_ctx, 0, sizeof(file_ctx)); - file_ctx.hFile = hFile; - file_ctx.file_size = file_size.QuadPart; - file_ctx.next_hash_offset = 0; - file_ctx.next_read_offset = 0; - strncpy(file_ctx.path, path, MAX_PATH - 1); - file_ctx.path[MAX_PATH - 1] = 0; - XXH3_128bits_reset(&file_ctx.hash_state); - - // Submit initial reads - submit_pending_reads(ctx, &file_ctx); - - if (file_ctx.reads_submitted > 0) { - UINT32 submitted = 0; - SubmitIoRing(ctx->ring, 0, 0, &submitted); - } - - // Main loop - while (file_ctx.reads_hashed < file_ctx.reads_submitted) { - // Process completions - process_completions(ctx, &file_ctx); - - // Hash buffers in sequential order (critical!) - hash_sequential_buffers(ctx, &file_ctx); - - // Submit more reads if needed - if (file_ctx.active_reads < NUM_BUFFERS_PER_THREAD && - file_ctx.next_read_offset < file_ctx.file_size) { - int new_reads = submit_pending_reads(ctx, &file_ctx); - if (new_reads > 0) { - UINT32 submitted = 0; - SubmitIoRing(ctx->ring, 0, 0, &submitted); + if (ctx->num_submissions >= NUM_BUFFERS_PER_THREAD) + return; } } - // Wait if nothing to hash and active reads exist - if (file_ctx.active_reads > 0 && file_ctx.buffers_ready == 0) { - wait_for_completions(ctx, &file_ctx); + // Add new file if possible + if (!*submitting) + return; + + if (ctx->active_files >= MAX_ACTIVE_FILES) + return; + + FileEntry *fe = mpmc_pop(file_queue); + if (!fe) { + *submitting = 0; + return; } + + FileReadContext *newf = fq_push(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); + fq_pop(fq); + continue; + } + + f = newf; + 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; } - // Final verification - if (file_ctx.bytes_hashed == file_ctx.file_size && - file_ctx.failed_reads == 0) { - XXH128_hash_t h = XXH3_128bits_digest(&file_ctx.hash_state); - snprintf(out_hex, HASH_STRLEN, "%016llx%016llx", - (unsigned long long)h.high64, (unsigned long long)h.low64); - } else { - if (file_ctx.bytes_hashed != file_ctx.file_size) { - atomic_fetch_add(&g_io_ring_fallbacks, 1); - } - xxh3_hash_file_stream(path, out_hex, temp_buffer); - } - - CloseHandle(hFile); + // Sleep(1); } // -------------------------- Hash worker I/O Ring --------------------------- static THREAD_RETURN hash_worker_io_ring(void *arg) { WorkerContext *ctx = (WorkerContext *)arg; - unsigned char *temp_buffer = (unsigned char *)malloc(READ_BLOCK); - char hash[HASH_STRLEN]; - if (!temp_buffer) - return THREAD_RETURN_VALUE; - - // Initialize I/O Ring for this thread + // 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()); - free(temp_buffer); return hash_worker(arg); } + // Initialize pipeline state + FileQueue fq; + memset(&fq, 0, sizeof(fq)); + + int submitting = 1; + + // Main pipeline loop for (;;) { - FileEntry *fe = mpmc_pop(ctx->file_queue); - if (!fe) + + // 1. Submit new reads + submit_pending_reads(ring_ctx, &fq, ctx, &submitting); + + UINT32 submitted = 0; + SubmitIoRing(ring_ctx->ring, 0, 0, &submitted); + + // 5. Avoid busy witing + wait_for_completions(ring_ctx); + + // 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); + } + + // debug + // printf("Free buffers: %d, Submissions: %d, Active files: %d\n", + // ring_ctx->free_count, ring_ctx->num_submissions, + // ring_ctx->active_files); + + // 4. Exit condition + if (!submitting && ring_ctx->active_files == 0 && + ring_ctx->num_submissions == 0) { break; - - // Pass the I/O Ring context to the hashing function - xxh3_hash_file_parallel(ring_ctx, fe->path, hash, temp_buffer); - - char created[32], modified[32]; - format_time(fe->created_time, created, sizeof(created)); - format_time(fe->modified_time, modified, sizeof(modified)); - - double size_kib = (double)fe->size_bytes / 1024.0; - - char stack_buf[1024]; - 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); - memcpy(dst, stack_buf, len); - atomic_fetch_add(&g_files_hashed, 1); + } } io_ring_cleanup_thread(); - free(temp_buffer); - return THREAD_RETURN_VALUE; } From b8e577b5bb06447cc8d275d0b899e57680fac869 Mon Sep 17 00:00:00 2001 From: amir Date: Wed, 15 Apr 2026 21:03:54 +0100 Subject: [PATCH 4/7] 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 zcmeFa4SZZxwKjgzHV{hd3?IcPC;?h&K_CSRt))7}jvlnZ2+g$!*cRH-DutLPr6`4z z>0l1Sct@q0i+GKSnClloJ`B~ilY*UqTo^utpcMzGdWKXP3P@0-@AIs+&&NzNMSSo3 z|G)3=XO*0@*Is+=wbxpE?X^G7o@?UsKQyMM#`6A+wcc+r6`ib*n8{dt*I4srSuty- z6~W(Qt@l{_0Xh!<#Y}GH(>BGtXPv<#nB{(G@++T57(DOkamXjjRr|N1UUR&s71cD$ z)$+B0fAFtC{`${H-c1b4@}A*xQeRB-t%_%6p3?E2;c}Es?+se6_vZ5FVqwOz>WRdE zhR-p0B`BL#{OmW%pCwIN-qVvOdE|dxMxK?=$;jhB?@3(#<^#aVtwX~-`@7&KOJ$}9b2FJ-QT;v zJo3F|%j!?Mbmc{-oqE!xOHa6Tits{`~$1dUsi?A)GnyQ(szU!ARq#)!!vBw} z(0NxC{6nkYKUxL<^eX&ZT7}N?D)7}+;2)_%f3OOCxC;EkRrtTP3jU|6@N;(+I-jV5 ze@GR0tcpFytMK!MDsb$L=fw@b!8_rN74Qk*lkivt+**10qK>wemtC@` z8rHfvSKWviC7TFaMT+Oc|twfyqdWtUl?-g-sH%B2?ST5>5XTGU2HE=8W# zj!$0>wihkBxc$o3Ws5Fa1Wb$VvpQB^x@=jik9IMD?aSE8%RAbw*2`C~TnlcNu35R7 zz_#Vm3O2UA?Me|EfETZBz06v*i&sGeA{(YwFH>@0 zx7`;8L_kESV)+$qE77J!i$LwN%h~iL?JF-Q%i6A06e!phi`Br4jo{m9)`#bxb>;^b zoqXac{@e6Z{nt|hXaJmkdXOJLPd;&m1yU{NoOSkxL9WqSbk>3<*pO%R3z|N7>E)L# zTd?G!OUddbt1iFHNO6&(j*5tWIR;VWSVW@y%Scq;YyG#eh%AYtc+B^(%VCzaKl;0e zH4bllA7?0QIgb`=(i+F1)B!@17&S-@z$Y-S?!og!k|F%7!T;_sxJrb(V&J6;c+tQ|D&Qxb zrgSVbfAsh{oq-$v)m6Z+H1KEze2anCSHQ>3R5}e6@CmaNKD7dVwt+WRz`tVPGb`Xv z8TjG~_@_;~R#d=u8+dC4{D%gq z!$Zb1AA)}%1oy^!E^o{W!M*XG@OTLBjrW9qC7tTX zAA++E?_Wa*F7suRF*OAD`V6xgLvT0(|KH3Ie7p~}tXK$cmKY@04#6jc@S8($I6D8| z!VvszKGd=nhv1PAd_@TEtw}JuH3UB>gugZf566R@A^0I7{H_q3>lNO=Ob9;71LD~o zf*%%w_k`eY55apwaI=&rxqTt{J3{#VA^1B(@N5WvcnCfif`=DD@*z0q$lkx95d7U9 z5YIvgeq;#lhTsh$cqs%wDg+-1!H*8XEz_@Q|Hp*jbs@MJLL@gDg1;w(Umt=W8-h24 z;8R2JsUi5Z5WF!2KQ08H8G;`lg2zJe_lDqh2+p+&?_YBWexe7&b72U6QV6~{1aAz% zSA^ixL-5uR{NxaPZ3uo!2;LcjpBjR9h2S$n@JtAPS_s}9f}b9O_k`dxL-5`Zd{zkF z7lOYp1n&>QXNTa~5PVJuJ{W?(KLpQ*;IR;VCzefTuMc(~aJF%$V@(q}TwDOE8q>Gv_s zsZ=p5>64k}6sp)K>EoE@)T!7b>7$wEl&P4J^gEg6RH@i0>4TZ(6sg!M>G4c+YE)b- z=`l=mN>pr?^k1(=np2@-OwzlV<`k&dDCy^!=G3RyAn9kB=9H%xmGrNf=2WL>N%~Qy zImIcK{?7J4z;pxC1xasXnp2u$UecSG=2WJbmGn=T<`kybC+Y7q&8bVVN7CPBdJ5AS zN#DdYr!d7%Nq?DXPF;$vlD?j4PFaeJC4DW^oT?O?CH*<3IYlYPB;Cd|rzXWlNq?GY zPDzRllD>#(PDP4QNq>@QPC<&6q|afRQ;%Zlb~!rWK4y)yPl7A?6p-jj z4%kzO9N9^{d(9L0UlRNo*Yh#$4cjT%1AqR2JuouXuGwxs@<#iiAaH^qP`9VBJaXi` zpgj5OoRdabtK*awJ27WGFfH5pefvZ^HRnBm@0m36xf;v87`obCyA*($3-1m11s^eN zdHDR3Uhxv(wt$AS?)6ms^(ow}8gLIx6lx-OXD_^$IpQLnbt85vK9cIpqCrm1y&uKy z*K98Ea%r2SNR5TTTye99&ccxl)0#3G?8T3n~8G~bCA<~dC}?9{qKip^|t zQ!Ou(owJHzN8djn(2Ilh)0s;dlr7S1DD;3{0e1OS-Z9p7Q{-I(=Q2xhX^OWz|F`6>qj(;n5 zY?`O4*p+J8Ws8{NBXj%F4$FCD6Cbpiul&`k4ShpoBBlm8gTYcNHX%`HNM<{1q|jpq zQEG~Xt{^>wG=|S39$T0g*@NC9u(+13OJ=V;SYD~&xa&lR$7P0VGExF{J8)F08nxhW zB2By$$VYC>8e2~6S`0z=gRMTOzqdl*x%hpo62vmY}3$#S0TXKeui9hIUPsQ`j zz+-=KN>0`uD2%gf@*r2R(=GSGFbh(^d5HXc6$5diRI?%55%s7S7ty3tCVjAkg6%}n z&wH(Q8c1)dWrqWQ0Yc?Aa>A=2twm|cqP0{LTQOpkD|Oz3I-$Qq07ss9bEBksaJLuw z7L1iD_qhwsg+(-i_8`g*N|WT7b6*#^!mQ(kR1AH52f7$#L}vuZ1+%ubnL5rT#7-@o zsW}UeiSL-}pf#kIb5BNP&cNKqcFuDOBD?+U8n^=(Kzv8KxrSw&=6QIy#C|`VqP!>W zYdnAM1->uV=F=?!=}d@I`QOUe$DKE_XOE9b^QKw5bMK*M=k^16&pnKBjKsEYCER`C z71RWW4n>UY=`c+hhk_%%Nh&F)1C0|EyPFsq+p6bpLOV|U^~Ac@EtoL|pM4Z#g{Ihw zpqST@rugLP&J12dfLEP7EJ@@hL&KK8RAWelN5{(U-SYAlWhdv}j1tlb!`usUhNu+v zw3lJckk177cQ3)qT8WWy?au}NwM;Edz}<_S$k6H7nTx;9YKNoBanAi6DaT*0!LTtq zzO&8ERH6DLm<(m9yIh%dp$hO<402z}WP-7z6;{xV?vhzrK6s<{ZuGN^?PR(r>{L8s zr<-YVz|Ld5 zOUbPQ8Po)_*XFe*RNN`A=&HxrsoGWT7@gB|PCFP6XJ8Wt<}D0B+&f{KTjlL0yrDAG zGAR7K%P_%CI9SPucHse+%p#u+>hWr>?ZumPfa|CMvLbm-ycbFQ2S(R#qroPe0IY z*9rr0U{1UbfIfRxJm0oavK)-CxwwoxhVYO_IFXH^K%pYL;Agv?$aX8T;cb+y+CH%0 zcZmnk$P6khi_~s;(epQOmaVj`(X1++p^PPjyl%{fY$VB3;h|>1Qjo%bY@SLTY83=+ zK_2h;FPuFD(2zYlKDhcYK@6+0c%nx?)hV_-D?ZpZ3flfYnr9BM9BQafNSea4<9(|m zVSuNC}@4Ja$qI> zw32>^qp$5&VO8yg%B8W0RE1ig+>qJp_*cQNlMkUq+`FFU8WYCl`B|CEa+u)5b<{*L@Ydhm% z$s1lnd@(#lNM0G)O4sXN3OyH}0bEDZ3z>WrN#Rf&tb~K)wp3nUxxTgg(b>^y<`5QV zfL^AhDS!U|S4(5z6aF97(so2fAuaus$$v&m3AYn7Q_Fa1@tB^<4h7A5CqTJhEWzEqQy| zHamSL<3CK|*A0a_$_Idgk&^)}P_>hwcoY6Z9GIr@R_P>+N{5e2#BZs=P(A+Y!?a_} z3z{ZtnC`~`C>{n}VKWu0LuIbKYfoVl#htT$&ri3o1;$4LxqO~bI~)>r1H)gZ)D_>k zJ-){pJ{;v-e{?$-(RJkR_#QN?D=u&EL+G_56nX{4Sl=fMTFgzz-PGTlw8iTfP zGx-34;_?x4ophakQUik_O=BH`QxzHc2ccZ>DS<&a+l(EkNrDk7B^M7PPz|GuCD#-^ zEu?C{gOlqB@(y$tB4~DMZ$vUx`$hcqmuFB5rxmCjQ;^{k@HS$nVo?M02=hTpC*i|DY}5v$|Dezs{8f~i;x9^aeMdfE~pS8UI_8{kcp&paW0TCi;MQ#{OZP`R4`3$L~mD( z`;z=JKFNY4*Vc+8ujB)gY%vYE^A93PNZm}kVIgG_d6H8Nc16{r0 zKn<|WNb*bI6$XZ2Jjy2&CeLi0!Urir!1HX9iC}W^9b_@5i{82TPR(=qu+#BA8r{)! zI9jDG0!qbfsqcOwQ9G$-)qvX2azvkMEI$+kuZ0Gu*)C?naJ0vC@LOeYn6Y3F>#qGC zALs|aCNkK_6r>-hP|y+oM`V|3ey?hfEBqi`9!GEfofpBGT@n{q_z(PeL6;e?79(Q> zTBTsxqmoEuZL+;ZoES0@8Vw(foRSe&$#l8XpOcpAJ_-rkvP4HcL%8c1S75@IuVq zBRJC*%SXke<`+=g+b|5*9)>x|dSy=^+P$U$!%xdt7$iO7gXH-@JD22(age4AI@(5k z{bWqH3SNB)?BoM>QnAI5!jXoNR5LUn>;o>lA?#b&M8;psN!}NfgQZY@kqtAc%*1Ar z#8l`wrczn~gKustIS6otHw;vG6Mh)&CVBn^0DZvZRBqY@dojxZ%2c8xHuIrYut0{G z8N2_6deXIj=L0?L-C|KW_r%`{MJZ(*6bx3WFs%~wvnjzKL^q7o1qBeC57;#icmQF# zQJEH7KnIc4ZT6B!U?ZKIX~b4LW~dvD%8Bkz0Kd6I*dwf%gHQ9CY-E`n;uzw~@&Tk0 zn<;av1G|x)TIeTllfgzs=_H4MQsjrcK;pYjFU%|A9p@3Y!^>M3vz@_^G0fBk$-_w& zNb4A-#dn!PjiN=VM+6en^7@F?*|WvkBiBBprl9Z&?LL6jXK2@E(ZpsxRJ&`iMT;2w z(7&QB>DnXtfOgL?+HL*~+oq*L+7&8+cKu|9c9juo*I)$N6@WCRxuP-0Yu9y>3&R;~ zjp$d(EY>Wsh;*}!Jn3J~cH4Q>v6;ouJtQJBb`Il_bqL=&e#>QMC%J;;5}Wz( z9neiUkLJS|a5c#YGh=u}1obI+K?t49H%+tr9}-_8ejf6-W#$cutaO^hkY#6gN`FNP}O8 zx?VaCpLE*B#6C!}ra`zx@wQ-k_D0f*Br*tOfyaiLc!}S$8;IvJlZPwJ5U+>0)`pwR zP{gY$Y9Fte4m+^V4qSFQd#!u1SMu2#u+!tlV_@z?tlJ4AyqQC0H6vU&0$DlnF5q@) zpL`aZoamFCf8nF{RX$*9tBtAs{7K*ot{HS+5D|btYNNDD7N(GD>D2QwrV$Khz4kQd zz#y%|>6svd)0N1EuPMLU`10(;HmvW2NR5_TMlZEd$()18M!KpIX&scHuO?hjJTRXV z^WpdhzWQh}PLj$l-V$h52T?C1s2oJ!0kQq!6^bqAZvK@QS?1Kon)(|+)lwr+<5|eL z&HnZy-!)KqIPso9I8&jf-XJ*>$vz}mTr5o%oV_HP%WXQ(GG!&%Bo@fQ6O&jd8!;wv zfgCh3iN#!3PJ;oo;?a^ANd}6~_&(|{6;`bWAu?t9L8Kgjp%~6heMx)vz@6;oI@2;t z;!O#Mu?Z)UZ&6W(5M1+7_%e8g9+jdIi~SN>(OqdpuMu zOj)Sf#Y*&0B>8PIL|I$Ri>H=GV)=AiBzc|W8Nl&qD?1UJKu=7t_r%~nYujqO9EgbG zc{*?p*RU z?Y_pxg%a9SX zDhV+Q3!Bw=7D=qaBvF8n8;410@2Xo~b2zwZxT_ElR{qiJcE=iBxQXso`M7U#s|^GWfjr zh#J4s;F}uSvDy{y)51mxo3&Gshk8f_&yRR>@)yzeeiZ0|Q2S*;^}8>DYo71v68IkJ1rW4u2P|k9nhFtscVikXfKcEZsX!J~5(uA(y>wEc-1Oyp{ z22~skUxLD#kht4qlm*>VFpo-r=X1V4caa#UC>Y*XTxW@UGwM`<-5fJ#6XB)gr0G-t zjqI?j_S5|d*RW+|#U#&g_~)IMJou(-YeqL;RN+Yf89oNwVgEH&Pq~?Qx+{Gbhfxx+ zj#`gYppS&R81l$g;K*ccNj4-$=nQF@=QA*Y4V!g7{UE?}v-P`rSg|9sP20Ikri$J? z5pZ~jR8oOV!NFp$5NCb3DJF~N+^ih(hK(yU^hcTIncxg+fI4Sw>-Z=V1VZovj1hPm zfhk$A=0FR`WQA}Y6OaXf@C-I^7Aj=ntZnT^pcDO?(3kW;LqTwLkyncpO#)3tazkrSlBp1)j z@NB#xP!6qyVIsyD3&q5Ujk2SFU;~pGqRzys3?|r&ueS)U7=}*EBFWE6kPrT3y#c}% zU5l|#<`hM~PB_y>L2V+`ZzT5^`+Km3gFX=N89oGvJ$UPur_|Jgx_hjSKLCrO#8O7{*gLQby{k!cG$k4TK*P!kDevKN!3$lXF*$n|QJZ)!n` zm7#2`jun6>Y#QAJ5OvkZ-6`4!arzXs&l?&N>-sD?jgXhZoJW40I`q$5LNYw(iuGT( zA$7l5Q;+d{zn6Nn>JhxN`wrsSFY>qs@)$k=yz>Pn=umTmW$YtG)=);+9|Ze@IOISQ z^d+{293b7&Z;LwyuPxmM3|%lPfso9y4L>+xO1h)B$z<{@Z*e`bs~+QKC`i0YMvxiX z{(!b)|DO+xa(_1i$s0KuOOu;At2ueU1Sj(q|I3R1(LXBw8wUR&gKwwKsz)AbofKb~Mi0o4Z;MN|g=S_^!@<<_Uf-#P2l5w$J9@Yu=B)W7^-t zzt^0MzgBx4{?@b~I6N6$=0t3YV&^VhJBu$|kD9@kC|;)XWq-Uh;>DL!1Dy!Ia7e{> zPUNj~XdQaH^dvD%Hwm392YcF&Osv~!b-V)~J=_B~p(pZXjR5xYr+e8uUiS$%I_q{J zsE0D?&YJR29wggTz?Wr+#!z4v3i$0`ivWPMNhQ?;j#~=;D#-hwMf1^2 z2cv!$yZ*G(aI*s*Qo*_W+qIx6f)Zs#qNtcn9|4<~IKGpwGPvd3wUiN(=6FG6Q^{;D zK0<=S*kVR8Z~q3|Gvvs*%OE%q-Crb#F)$Gy;WTVSPUq<WU9{t{c9feLws?spH*=b-Sz%tcijuSMV?fY`0EAS#gFOxqUc} zT@H6e4OtQeF`U%yA)q!Q6xp1 zA&5uSYw-VkJ(-?xf z;?D_$6W7{`x<76x%#1(Rv2&P5)y4ky!aL4=ejBy9Zyww4*VCCUj z;dvZ+Ls!KJF;JMGOEB0RnE=Nvy$2<*7xl1P%Ca+7po!z-rqucBeYDjOXEf!Xl~BR8x?5DJcw^m~3Qbk_^r^}rH1a)v)Y*0}B{td0Bx^GNit zZd3w9PS4ce#J7Ur8GwQWqNj8VhlGAJw$I!G2?}V=8Y$ml%0FT|f2Fzq6b(n&vT@-% zC}fyhzbgxS#w=(dx&>xI&GOi2<59_IT6fb@x*w>ag#+GTMw3y)pl)_60jd-%!gcD7 zBaYZ&R-BYfbzokOrZSZ*S^FbobR(XfZ9kr+X-DZfiKi*V4tCrE zCn+KXmE+%0X81@5`Xa%hDg1p8 zHQfZ#>l6&rC7cXSADN<^e1-o~LXzUV6MZHO(VdDBe56(fbWemh=GbZo_M@8 z3oq27lm}({YkEL6RDRbw(|3XSF8N>>8uYLcm|&^yF_Z&PD}a!1`Wae?&r{7b8O|0n z5PbWY=BO$QAWL@u13uIn+N5I5+AMNpmLK1#RXCe35H*H7i|wRi09NZWf|^RQ+&$=T z{M7Mjv=}CWQ!ZZD5fk7%$Ll(z=#T!jGGI|(;f0u?O};6t--TPeEobu)_zDU=IVz8h zb@E7Cc#OgvGgE?^O0t~C++1NUlH^Da8zx#hJyjkXOY*p@5s$N+!Fy;OXwC<~2%2*N zY*_Fx#5Q~H5}CKpMUre_6rrUHR;mtf%A%;_ynDz{7)>(85{6GmZB$O?^!}O#beB-m z^5tFqU@)UG)96zBHmdO*VDu;ub2g7WjjElyN_bQXDz#BE)5-krh$EA#t6vvRnE@e< z1qI`-m&4|;p$W{yX8zq1*)YDAQe*;xT0@(}E*VldM8F453EVL*gs}nK6`uqIl87Qd za(C>IGfwQ75hM@gc->KkaVRjbmf;ncM2vF-40M4*I2?4ricot!Ygc4DZKK>qfp{$x zUCc%cbIEaJ5dQ+w_*5k82t2V_aw8jFK}(3O4Lr-b+%GBvty}3 z4kzNIlX9bov3hVL5Eb-e_d^dAx_Jzn`Z4JHHd+minX;XU&3r)Sv$c!R$+1iR z5)6*=snwwX`>}EZ26+s*I%UAKgt=-x3t!E(Ep1h}H4l|=UD}K&7AHx+rk1Akv9! zSc~pkybOPUD}`aWINwJy>2#J4?QidT1Fs$mHY(ETPGoLskOqt7FWDW&s0T(*1xCCK zgD0{6@ddBbpVS4f-I0Tmtz^sal3v?jUgxK*`N|c-enClZlm?_{{NWL_4;>oiK4GUf zldn%Kw_d03+CD7z;>Qn)NI>LeEcFC~BNiw98)*E|ADoU{v}VT4Ju z8ekJ_g!ceQ(nk(VhmS0t2~oON^o5K7HcKOzxo2-9kX6c@dkT&Y;oR5?U;x*W^*~Aj zY43e4-mx@TI|(a!kP>!?(zRpQa4&+IhbjS^0VdGlyV05PA70IFoIZFSEM5B!#pwkO z{@WX;4>*5s6gvID!GC+>po7;wqc{*=d=U2uNfo-?0O^8X+cd8a$Niy@tFvG%k4Es% z1=2dUWQdTV<&l%(naH&pC@XHT%o1s==HfvXM;a*_ls3C<6cREz-;#|z_qb)QHbjHH*a zzi~6HeHjpW;NTzY2tw5$-_K>@!M&D=1^fV(iSOs~Fs0Wue1I{Lcvnsq8K#1B1sx~p z-I4R+UArUiMfk~?)YkLavi;C9XmA2r;9JlbFrQ1#XKV3q_@J%F5Oc>5Ap?Qdq~tMM z$XxXdia+*HsQA4gsgpHFmMlPk`~L3Ng#hR9Ai&3LVe6`AAn+zI7Onj_TrId9q>T&$ zU^IhWdVX@Ap7U8$vpg~tZulXJJ1ZB}x#zM8soDuRro?e(1GqQ3l8xp*EKT;bN472{ z5BDOjDn5;nlHKNG%C&nD7!zM!<}`6H**(bchCI0`%-wnhsXT)!V1&AUa*Zsc$%Nl5 ztpS1+6^Gvkm~{YG=A~}RN*_vlb@eVdZ@t%>?w9SUJ9jOGh$;q)Er zgHQiZWKR*B2JnL|ozkXzu+|5;Jo%FS18hyzH8P$g<(jM$-{6@tkQEBaLWGZepOdh5 zU?n#5uXSZJ#C80Ci;~?_en5FkAJ9UcvqZ@!A+hq3xPwL2h+GGBp#a1aAMv2M&I0)3 zKLg2ix|$9EvS_Rr(*6DO!J{f|9N6=fhVguzi|q*6HDFjo55{#8-u<)_W;JAd_tR{0 z{N=>Dm#r($NyT5r0nVIzp2#={<9hgbe6)`H_R=$29z=j%wla2y$fNd$K!J@eLq0Ds zx)2QOQJ0r!_n;e`qo4@(V$o?SU|(a1=HYQEpSGK&_Rn#zq@6d~kaG>q?m^99dYD($ zjL`OHhFWo|*NU&8ewjMe9w01EY!DVXnu`G4-M(5T!Cm2e#sPY@;E>rm@mX!~qe~;k zFoQNZPj&WUM-ZzQJU%Hb{Ys<*raOlq5%9S4zi2+1ITbZ~@0a$PUg%f17}X6r9rrm{ zvvr#KaRp)@u2$?tfkBlJg#`s-9g_FE;GhBpTl|HX3Ku64Gc!1IAkhTBotFKpU#b|}jWe$+(t6P+j(Cida}s%)Y*eyi2*TLeY2^a3Wj4p< zOfat!cm*~i<7lNrp87q%yTT3gKr#30RO~HiWj3koovk$rnq_}e97se+HX#QGao3_g4?`SArDad zj*R`uAwuz)5{b5>#TyNpz?GHzfC*Fi94Hg&PkYb2&F7iy^~l`YRGxE}ePy70)Z9ny^!4K)!jz<)1zqS|6$r+Krkl^udL1sBumxJizWC8HFN+##r-7+({kT}P zXs<6uX&cT#;d01%-d_KFo#c#3NW*Fpo5|dfkuM$sziukab#}x28qj(h%XDm+GO#u( zwTmm|P-BKpxbU2N9c-CBP}Xpefr}Sv_dCWjOm$+C$IV)`xj%5MXAu-X{9AbUPH5M7 zL8k9FDVM;Lt<|nhpgJUUbtVMtzfo!Hw~6BCW#IQ;rkCN~@qSoxg$7YETx}v-XPY1^ z7n`c$0k=8-;401^G({tQKhN=m+F@F`$;=TU#ioKyr?(DXJ5k1$CLe_nC4X6RpT0v&;Y&h)H7-eJgk zhJzVe8~B%M+8zQ4l#oNf)Cv%o77g*8i2SUxgAuP+Ia_6{Nc9ihc4Xm`!v_V5nDn!O z5EphY5vPC>!YT?GMB|858Zde~@Pgdjl!K@Fuqupl*|~;%AAoqS0`g zLK0{WyBY|HA{!>c4Q7B|c6#CW-w1i2D0&NAM@NZRn;6FUqtpriDV~JVFTr#=wP`>U z`#>RiB8?(3aE31N+EXlo&I(VD{)?#0ze;#6A)@8fqz}FuE(|t>i&l6NDQe6ev*~P% zWM(abG4VVUGilKh3YuVe{jNMZl9A+p1sFKG4efo>!#Dgc0KbS6T9uQvQcGG=n5Z(9o-t=LP}0dP zQdL%w|MO<39Raqf+m89K%{B;3$WFovx4R$!c0hVj_43uM^-5k^P_Wi3 zC^P;FhXsjpZ%@{G`7}##n0xKsA`36xTJPMigRHLg0uO7wq#uVPF%&$c@8n~zk!3ND}?j!q#NWvmPo}KVBB4@5F`X*yl5t} z;VF}ko>))kXQ4D(Pa02fseKJ18ou1Zr!ga9L z&Tc^cv6cz>gaCC&OXWzFN?|Prn^6m#s2PEi#i*@Lc2s=BV6U>nnxX-d3YAH$=bWd= z)@7hfcn*HRgZAqP=?{IKQW)PvZm{6{l_^FK80IcAl^PkKtd5gdKBFZy4NAURq+9~Q zrgD+xXxXh)OZ)tbLKUMDgHw&}Fe)*D$SF9VLsMM{Q|*VT_Cd|P=DMWfi!jSvoC7F^ zvBx=osF+pvG3tXYZ;wB06|os7I`Y~X7;&9xzO;tlfF21{ltF|?Lw~DAF1r5z#)up4 ztZu~HfASWM_y=#N5sR%r)Y1`Nds!9l$&4dYKLY`AEhe;tJuI(c4_NeQ>4iPeyA+Iv z`fr^*s68C~82p~Z4t-kVSLw>IgcUZ5eq$8l%oXH)DOsSvwd0W+UdPlZ6EYU;|7Pe+ zoON9=mrmP}I~(>Mg4fDa3|1>w4#a_&bHD#*)Xl9a-k=uQ&;={dy%K(_fcypO8vBsn zhy3?wkLxy$2+z(wf~L_Ip~bbkCqbaIA_;I&8nR5!vn<&A8HUd5+Gu3e|zrz_RTX)cG0 z+q5=7=qv#F_a24xvN6YFF%AMS-#kUx%$yV@Tf;&rn#R(lnRTU zilq~^k)|%qm0jkjoD5L{KLY`rJe8v4JZ|_=_ur*PTfB`1>Yb}74|#Wg*&B`Vvruw! z0fDaV;N(KcUa^4>DEH%WL_v-MxKsZ`&gbD208S{(w0KD+6EaKhz?tLPqh$QTX;f5T z%Hbzi3Rr_?P%GyZHpRk0^$-wqIDSA0@q9s}ot}@=5kgkIc+cxfYNpO1HoY5|v+|LT zZ37?g^^4-L!{k)^WbFKtAvx^87UkUDKcYBtJ(^-!&%m-!bSi75AV;gW8KYGKI5jxQ zV1wmZc)#j#q3_#6@+|4hsM0rx|oj_atKxqI41_@h;G&*7(54I9ScK<4Y8{~NS$R|$`hy5fUSl~b^tdvnPNSN%;E zxpK)U-XMAWKpFldHZ$V`k$=Ad37oe=BKQ&t!6qqk;DSCOIs0KohWkQ7?0L=K_N3CJ zs_SDD?3zb5@i4uKb)Ncn(vW;TP@j%r`PsEkIH+|T&H!5p$*V~ypIY1OBpVpH;cN&^ zImXE!*cHsD_C*<5%r4EP8Cpi|l!NHYa_%=EIXgA!JxpR|aSaz3JJkrrFbjQayiA;m zUpUX+JUb zHQKK|#5FWxn%1W$twL>bkCmw2y$zDdB@YD#N?F4*OdY9>1nE*Bgo)4YYR{H7({8n7 z2TCTSBzBF7JzFZeYO}isM;k$>VI3$yAg^k&(IojBXWadcOiMNcgvVliwCbaicsa=f zM=`uE1O%@&x#U{BvCX+;mw7`yq{4b4vSE^J04wh29smnC@4!ajzylxHZNU6N02>Wl zQt63_+bJ-9PoXXQ`WJCP?cB*!HM24ivsoD$hW`?9%3-2;2eHbrh zRKmAdsoFojjn+|H;3J*PfMrbRqX6aH(tn7!GFOLSI|#$PhU{VntId#yvrTW<*tnKJ z{S2s|vvK(#xh607WS9~3FZO4$xPj|F_Qynae|x|kixT*`gk)!kqR<$i!T{8J)YW>9 z{ILjeDsgkk7#^s`D&EJ+0rgQ_;~;f%_Xik};?#JOSMbvqfcCwW~d!m8IuAh*6ju z%142)ZYX~n)U}>2*5m81i$Y1I<$<$m{Bqn$W(ag1+KSR3gQc9m+L2qfje&y3*fk~l zAW7~gOSmW_P(Gl&f2Pc~w{9cd!gW%(u4*HH<1Vt3Y$R?jY2(56=%-sLDT|JkN-M36 z%x6?5t>FVd&^Q?D!f5OF;utoBl| z(0#HGQyZl*V$~uld>BJfNJpVnKf!6CTJ^u-K&Y|m-Mp(CAK4H!J#oX2 z%B}kG>pg3G=M5;G5b4vZ1%I{(KXUCF1dq97y{DL4Xl*)<;BY(%%XSZZJSK`cH|CI1 zmi8H?TTLZ}@idOPV^j>fgnWFq=f6bv^+X(vWO@Q(&y9wTU}+0ygK=b-av2uaNP8<* zXA;6Ih7>ZVLrUa^kFaavWH0$mZS3MhOFXe185k@8q21xjBOi+}Y-EYYLCe+>Ga;Ci zk>sk3+J1?2E3hMXV@XLAsl=PHB5T9{9{?>l$sSM0mD z(v7TNovP97z@4O*Y?KD)+}Ze&kq4@>Al@s;weQfX`k2Dibn;-`tQ7!xILUrNp7hlp zP?fbAJhrT*TD_6^H(x+Ksf~h`GNdQ2A-3~`9M9rNC+_feV&X<#5Y$5$*9lh6b+0qA zVev)I=%~`>MgSpp57Jk;l>l4Or(L`MvqPe_;9V7D3U@<5_7_b0gzX1UuoK!n)zSDXdJ;b}#j zs?hH6ZRCqwY-a~%#kf`=xEkm~ESp|dI)CmbFFY%K(TMjOz8FsQqi(A6OOT3xIH)Y= z*aT+5Cd>N@JlGt=SQZc&g5G^1WjR}r6B+dLqyqe4BF?bYbiB>xMhXDXP3Nk-IF<5Bu-3N{^|K*wx^9|Df<;SYSzmR+n z2Wj$A5A|wGw>Hv|@!Jp6!@Mu0*`0vN9C2a3V4@yvm|h9Yj%*?c6O#0G25!kv^7^Zf z>hCd|SQjg=un`ry&;%qXJ5o3R8iCA*%mFXXFhRwoS+@*{-MP~D z-4uR^8+eh(*AV^_Au)pY>;Z%LGV=iTQr8^MPQ|7oOK@US9W%9=hN}i3mi#Wie4$I|`5!z##dYrQnbZ_A0^eLVaHU<7%Vn{Fe`47Qrz@0 z_&WMPoaEbP*ANZ=4P=irHZ)>y`T_=0j<$kf4rNtzX+8cy`;kQgcO!Z8v^d=mEhn_; zVpPg8IWf4IV}?&`axO#M$)3UD+S~ z`PkB#MB8#FRNI+0t@|Loa zXt!lTf~YCU{Sw<5rR1l}N}{`ZCD9e7&W=z?aArb-gK0HP1))LsN;zb?B=tyEC7_)c z0K^EYIzog`gb4ve>L17+92gqNs2Y`<>B5azsi7IpJ)}GrS>fDQxY=UdEow(y;au~( zYa?LYGj5Ft{ZYqhdK@DO1|E+14E&Z29Xb4|R15Y^yb=EhslxOu-U%S}CE^8o&JkEQ znuGravWHVH{bk*?qL-n0tA^@W)Baa{osY4R>%N6CwyZuIJIQ+yVlAk0oT=*VQkCC1 z=sPy~{m(?UsA}9W2RX!^5;hggHAtI&fnItN{Eqb0fd2#c=HFqptA=4u&bohGWhhJ4#g5v&J6-_PV2m8~b{o z&X232=o{+3w=QH{H5D6j6;7DO0NF67Jj_9V^^WM7xQZl9T($6uz2d6CJ%Z#W;`)Kr zC&4uuDuETAC7q$W-@&L|UT5RJL#rE2XrGzrFxNDvn?#XdC>S@ewl;5sA1D$jnYQs5>(et#p**{B8Wd(RznKlh-wi%l93S+065b1EcsG_N(~fbi!f0 zK$OSl9P~HuDUmmi_5z+FJU<<0wK*mc$f>(OZS_vy%i(D`0t|E+-Y)gRAN`d_ZNv0@ z&`w@V09>JNC$DT)x_9^~HL!rQN%M@t}E)-Mhh|K75zr=`#GNKPQ zanY6rgNoI5Z*&UApO8C{=`g~*2O=0g>h0-yiIp}@`Ca$BmmTqwZ&uPpG@KEP_E-r_}pr$<31XZ#spN_eu(@ zJA~BZw8r!ysRt)^z-HXVX=|thm7CrLtc9>WbF&-t>HiD)PVVZ@35KBby z9W>Q}XB$i_K+r`qVCrlJzFZJrAh!BPj%sDCSqK&R<0(|s1LphLkY4`jtCQCBMWr>p z67ztAn9meJ4P@#BVd`Gn`8BG;0V2wiH4|Yf5hj$CX(Z@13|`uh9$>Q@f|H#4EWxMn z;E(jgJ_w01l`1Lc-mBTH0KFC{bRX1tpGJWQK#mxLc59~g(ODebaWfes3`z&JTu)G$ zD8OcDfq++rB36s?PaUTFywVXv#XAue8{dGH1VigK4Ee+Bh&lF6Fn z0BjAPEbU5UXOhv!cpOtqcL=hK5tJne7I2Ch)SZ+_SyNC@KLAC?qZZI|v{(okp&8%4 zMq!i+nydc)BEpV`{G}_<^dRe;`%!{vPSCAb*VBalj)Q|=x9Rf`uvupDKB!s0F8>9( z2ugI#czgZ2LMM`9RfSO|oj_}dT*q$|St1s>?mOmn@fsXmM03)ui}A$zgx!dz7yz!F z{5wN09fOF{iY$hsSw-b7l|JgD<)|@5u^U1t)M|)YGqOkx+GoXO)Hal(k_vf?hNvwm zM`<+F3Zqav%}1s3%qQNuM|rrPB|=~Y5; zsPKLCPzaQKDgfAImQ2?uHV*QkznRFD+GN%wuzRMzuw;Ja&-}Wa{=lXFG#EBI<#+Kww%96B zwxluQ$hIZ+oy1forRU{A;81V~On$3qP(16$Fk;|nZ?)fh8 zoiMzY@ZLlK-(DC1^+toN&(`d8z+xd7FAFCD!gy&o%kobXpf3+(fZc4c$>;J&YxZow z7UO*{p3SdJp)i^i?;%y<;k}Op3E?9H5;yptm1n})~vIegtV)K5N z5EJWbA-9aTlU6b9$FRyf(bi-`49@zJ3=!2!xfbpepf88+_o*4<5dGX;!jZvjhVt36RPq z+wqqRfGK&yg&|0L?^lK1J45euf6F762`-*#;z3@IERS52XPUUoSG`^Rug7Aut;ID26V(|vqI|fB%jbtDVMwfZzrKN6;xtv1T2p{CN`8WM<*5O zO(1wpZo?}Xt!OX>vhO{iP2CL-ul zJA|6H!cZu~{5~bn!?OI-1gLh1219|Rj46i-bg}^dq-nQanMPBFPt$*JN)c5wsK}(J zP-=uNkGwRH*-HX}0@CClGEMGY1+iwOElDz>X}^b1j_e_PqK6!Vhu=e-b=`;&GP(FI z6TkBaD19k8bPjTyh4{jKMdH=A)rlgwh*IkVX?F0!Dw=b`=o( z^m0JU`i<-LewhJKxCNLcw()p|TO-?Wwt5zPqQ>lCR)c^XUL$ZWGWX!cung5l$w7O( zmGCfiEfz&FI5W3FZUg~^^r^f}g$jkfj^K8G$#|?Mpnj|$L(kr13X&)fxxK68@u4T3 z9p~RcjJ=iR<@R{2x+8>)fYp(Ud`%n7N=!I0=_ z6;$Ahzo%#*sq$=5V2TD$&p)z-PLAleyddZVmb~qbO%;1K;c7sKk=F=z47@QSyqe)j z{Rl`LG;w$!7-qP^8xs0&D}+7-BWAyR22mrMi<|Pv-4^+8?H(qap1|HhR0TC{#hgV4%T*u^bp!%|@Rs z%#QDj+^`aj3N>UgdLt$fSW6Pu(}X*N;=Q;xs0X~DOSm!E_gF9iYs7ytaEpSmnBmsw zQQTKK+X3V2*Y!&LY(9h={uqkn;R1j!4gmq@shET?qUY*!d~iO!kCU6bhvGr+k*P|b zKejjN=>z@Z3?j z0DKfK`VALZ)efzUjNOALd?O4=;t&W-&N@%V@`y32xeMsqz_5SCfGab~RK4(NCYQC( zL95dl)VuRj4Uz+se&JbsygMCIk~w7x%OL#x_WZdQ_^aNHK3(qKQxj~Yv0d*uJQF!s z;qrXFZ}|clV5K-+BTPLJ*P5u4KZL>xG)7A_;aNOR1I6g8ofGqw`?9n&6P0M zlMm31R7lz@eqSew_o6ry-O>$jpMiaK!M-{xwl4Z*W0s>?u&cvKS--#Wf??6__cva+ zzS8gSt77!%mkm}LYQMkd;=~L6;u>I{Ft#q-T9JVYyPtUoT28 zD0q7xLG8Z)bx(MsQv!*Dh0???l2Urok_7yZ*+kKMH}1*8_fV5p1k`Y2y9UNnsd?uv z1~Zs`iH~#AQKaVUG-mfb*+~{c{m)#>`jucRN%9q3yohj} zKOCx+;jFC|XDw=tdmgDIx>0fVLG)(M0V>pR`UP($JgDJwaEOrMLtK$dPy7fUT>Ulz z3^cJlkev>)d+U@loAv94%-T*VdIi_V=#PBGQVBPqI}Kj}#8M~Ja>Y@wLg17efU&*1 zu_`+}pQxNJ?Gtr6#=&9If6*OT!*65Vp+&z!@wiNaIaeCr6D7*Besq4drbN}4_ zEGi9uAGaqgf(D-e9RbW5LL4tjD6ihX`b9?v2%x_@ne?2qkqJHA+(}-7Mu3XPPnj84 zQH3uu{2J%PW~Bwez-z%J&Xp~gfZSA6$uIkGSW(sGV;VJ_0vF&5t`Y4XlA+!#@?60s z?)?5&fM1Mn{BGnJJh5=Wja&Y1i@0bR-*NV-jm<&@K+*C4ykDWg951oJWAS$jkYg?p zvX5e@lII4X3}O@q+^Yl%BY4H7Bh=8V2s^NK!le@UdoqCKn64;O!?k59LA9fKEmTH0Z~;fiRt4?OOVZXDXS#8CIv7|!gv&r)ZkB*zZTRdT#K zrv#`liW?TD@^ml$0gT&>I%fvSk}un+*c1^^-&ybn<=ktwEGL=WTvl)V@b%|~*=qzP zSS3RdNZVU-0Cs;piyUuQL(h6U)0DlM8!~ha4m@l#o4*gc7cN8#F<@@dVX2Yt_E!JA zfg?w`+|ZdBN0vKXdrL4vwbpB?V3gvu%yIV~9jWZ8lD#Duty-m;tvX_@ohjLJn@@(a z1kVL4vJp%1dFD77Ug%_S(BcdQjBcPS&1zIx$#yagK4z!9N=QeQLt+`qCy0zy!iJ z00%`g0!Cy`);TSxf#2f$Gz4bqXh01in>w691nxttZw91b=TJQukbO}6svxiB7`;|d z3Miu$Q4}Bawz(Xp^2U>RcLA4OuV6c}7KE^71?*w3cxGLvg&)2(mjR2Mi!S>KeixF` z>9SLqsPr4XkXL4rUFa_gjCZ9vEo9G;s0!$s+{EOxZSc;srC8fSA(hUQqjB+_3;*V0 z{&=UD-^1?R3&iahVkTc}#;+uyI^{Dae6@69v1%=YC>bESm1%Lb7#rl>z(l|?x#$-! z#Mg9}Cm4uZJhedk>d-U<%)zmpEQyjrsJYF1WDG=?fP_C6$ z)@D(ASm3#HL^7S|92K0hlvB^42 z|E)O`^EAk~>`nU8bH3$*(e%W3@lh6}VbILESBAr-@}h#QW4AG~o1j6z*Jh}WH@OrA z@$+-L0zEP6KjlpjUJOf?=r^CkB(ZaLtU+T~ZOyhuYvvnYfW=@7ykrM7QhNdyP zpQv;-bP82Lvg-##tXv?&2{Ft7ho>7ZR3LUN%oKksIxUV}_;R+p&&!d6^8WL)CQm3_ z2wDND^oEx`Fs#U#68yXYrw=`P_NUuIMZI#!=PtbU_-B85p*aEHEnuA=_`shcZokVr z`*XK<_UESu&_DE;vp@Y6if!__BhLyiF*1c*5Xlu;|qn(R6j z*gY63fM2}KxdlK_06#8)zJ%z5YxbweFrUZgH+UTcbO*u-K$2>?FE}kU2-IH4BFfS$9wxIC4?*<=wGf2$y70N4JP>#~1PzpiqN+oa z=-5_h#6NOR2K=tfXkDh&^y)=;IyoWp)5j2&c=>*AvtPGg% z14qniWY%G2*fR+lE7(S&#^m}%nWNEsJl58kx5M3e*`Y`b( zELX0Bwc=0R8g3Lp+<>Q$Poo6+cVk9Xe2}&SoUx9q_i zI@vlhaUMbGL&t*ILeMb>^$LHmrZsE|Sfmf1$3(6~1RV4@l6X4Vzv zAv!vRKQkbp2Gy&wWXUUuKpl0W6= z`|=nB`$HZ-)nUcGST^XBxA0m^k7z^Bnw2dAs6V42uAI>_>Kk?7{<@D>@ZU8pPwFw(`QVQBbf38lcWIP`w%}M zYS~NO2~KN_s1qQ&e%+35k{ABq0py=^&v?`zP*O<0SJT)L-O)oHa0&sHW-dnPk0Spp z8?VD9h1np{S@G=ZZ-ZtpqfNVJL^KBSZDLOz&8lJ(jq26t-OQ?{j7nT^r=-REEZ|PDuCj!OCz`{n@K9(hyOYiM z5@E+csN^@f8##(8-q(q0d&AKyO!5EjO}i7HK~sWFJA{rrP{ZpaWs;C{e?W(W-CN9K zCQO+Mqr^O>?o2Oahs&j?(MPfLM~7l|?XbmhE1i3K;?eS$^vWe15p{jAVwa0&vi#0G zDm6?U7Ydm*aGs55x?3SQ2IZ&Yj?b~WFH%BJVENK4CBv&La5$nW$~AK5z|t<#cN7^$2hcMU4GZ#jV8RK3LB zJlMNJJhXy`|Ka7B3!q^{1!c=IXP{1uB;DQ`5+?|W5geBw$Xg=!bJJ^M(5hLm;q@DA z<2znMSNJE_Y?jkEy|p!)T?}F>27vv~2uTi#!L$PIUq3&dk6$5^(SV}@B18w5jE zSpx#dVui3}M`GO$j97?xhTkLOI1S8{KqUw^Ii>MPID^yj;Tr%@5rm#KTUo(}fQ%s` zgJ2FEAU2R!n4?T$(da0kBZ#8V0)}~Gyu+T}@i`OheRK;1t5JmO&2vsy7-Jl=76iEv zGUZvFBV^{>r#DkqXwh^=F969@`(`f2Fli=e{fdQ&lwt{3Fx!9`Y-zTl&a!Bxau- zesJ4^c(4y`$v``H3}1Lm-xyu#z#ee`pMy zlp9lK?Sq1s4^!qKB*9U!ejGIb_Uw4J?FU*LK5J!FDMVc9&qCK&maTbF8Ux}|bPS3L zAE3(c>rgNJa%OtHkJlHw!kQd1!s(E^Z6Lt%aOG;2JtU)Fyt!nT9!`FYg~kB20c}<2T7@RF8R3f0+#P_o^M^jZ;D>OvT0SEn?)yNeH5 z&bhE3(2PR66^f=`I0$HuLS^MfzQen42vFLZ?Q~PbGz>SqQvaUQ**b5H6;Upljs9n5 zqiZ3Hiowg|yzR801DOKVZfzMtyL}>%bwD0I;Gek|?O(T{m0fDG1>FZ72`(5vRqO47r=b1iWk5j{lD0I6Zj~Kb#1(BdZuS4>kzU5Aq*i20Rn_QEP=2G2pIOD z2m}a(V>XkpC`(WfgAgTv5Is@ij*1u+5fwdA*;Lf1sHhx`iWn3X6ciBX|MOOL_w+=D zS(A?@!$+|mr|)L)Egf9%Z>XcMXz51N+-tXp zYk(n*mkqX3TnB6od7nB{LD5%Fow<7IjOR>R?6F-vbq0M5Zyn$^VQjgd<(u`!a~IDc zdPnIe?9!~XN46vuV_r#YW!mZqvDhjbLct-iHT-Ht{Q&-jsv$%ViN%&1K4zY}lvW_k zJasXxbY(2wAVgmrf}G%Y(bX<5-hn9rB`pw1^<1(Mt0#ag2lSbFYH3=*+Smg;0WS5} z`Ds6+U|wXJM`Rh2?ScmC-m~iufF4o2F_vhzmbA>1RL?~^<6T%v0O5K9D&3^L>j7&C@38O2vODC45Io22QNXfFLybF^RQr^UU# zNppb%oP;{U#r4wRSrQPR*r<^iRebP*+8c>DN#XGo5@~UGX z&{ZnMnBY;#p%?CnC7pS!?!UaPP5vuf2r_8M!*Ep+TShIqPPh;p=V4TXH*y&t%Vz#^ zF>T6_*xxm~l8N^XiQOXY`FiOh;LKkyruC_1#1@dpG#cT^3y}Q&CwO*rGf!)q8jBh^ zcqfu^0iw}0StNj8io9ZgJ2T>s*Yz>Sfdq8^%}}b*kl3xfGIYghtGBHAuwkT2Iftw01(T_PRB#sBfj+ka&OR0~PO+nBi+TAsnlP zn-H(*vm^!a^d!%wRTK{qc}44qsNZ|3K-zn;bVw}bo!;r{v~*Y<4T(K$q;u@OSb)^X zvodxo#Pc8`5j-G*YxQ_7=FHEvyo3av6rb9tOhc6$Y9d+;(|gb5r^oKtdl9Ep@eFCE z3~72%h(!Jz5i~ADKiPZcbgjsm!?QN*y@(UiM1d%%aSdxxL!_hni}nVnES)o)|sQx|MVz!f1i}P$go7G zMa2469}5iEPvasJL_>P}qeCyDLH89NqnD7wbK;Yn0x3|LYPVh)ir|&0)+;0KmZm&q zLMklCMQjy8qXUXbjPCuyHIZ8vLl*EBkfRIGF1)*s$#I37l0}gNT2~obQv}z#YVSpU zK-y*75@P|cChlI`(a8V~QMevM11QCq`@mM-2ezUQ9EMYR`T!pqw<5J^)($7C=Enol zJqU;YQ;cK;=?3sYt`!Ym6)0a`fJSTaJFYHEp#uHf@bH7A+zMfgE7~ z-8;*4duZ|6UnSU0-Bi|DiatxyIFFqOH}{GJZ?S2*SS?<20x^6U5>rXy zNfLC@EHRZ!6isGyBZn?gi}y5~S;rY9k>5=k%o1mCiNYHUi3da<(7mKU^E_`I8Y){ zgHk>9H;AqtT#8u>op4BR(BM!&oJn}~;8H$k!nWclL)AS;j^cbqEX(#cJbwh6 zK;qq7GhMIIn~1)8a4CQD?LRlzUW40}uU)pwJ*^pvnFqZR%U_4a!KJ+Iu5fVaY5Gcv ze!PvReR%`oEjs;CF|B2&y|d^mN+RyYTs^q-&&Q`?4*y?&aH-=kTJ=u{m;Qp+{yYblrhS1l|E&j?wuc7B zTNrNsF0%fGKKinP=ePiIF7?UF9$cDUJ^`yh?86UKi_T6=Tm9|8akNdxd+{r2G^VV6 zp1(D|`tD>N;%}DYl&J)SaCj!Yoo;@0{Xmym?Qm0mM7Zgch}E}ck^s{Yr{aWv(=j#)8+bcr)BBlBu<>GDc*Z8=en-BTfFc7Jg-N&P>2pd<=+GJ ze2cE~f@n&-T2I;Wy&f&25Tj)jUo|Mll#ZTJn_unU?tm%idmrW>zxTL_&9KuFm%XqUfOC% zrfma=pAefsTM6!TEFmM)apf{K>B`t-B*!-ruzi;x%}+{Ovm`b$4LcBSd_C{OPE;xh z!7?_vQMuLFw+6Zajujfw*rBk4*!=Gu-2m*7a6gdGPiRsXu_ZIq6mVIku6lNxzQ zoROv-)@X_`pVvtFi*R&=$gZE{W=f3nCHR-)#5vY$wC|zuL&Ppxs3(}x;*Lr3=&T7I zYd*fQ0UZuJ)ex*u8$NPdvWO71GEPqm)iqjC3%BE&aQInPz4F>IYWkVlJg>Mtzm&?S zEd1+$(;BNshdX{vyjf?DF;?>*CejPJk}1S|!fc9P7_Sy5m>_gzwPW`*18XPmiI9Ha zqQ+?lK#FRa=P3@@NabG-^gaf1mYA`)HJ$gHPJ$FV^wrJFt=vIGYNtYqV=hI46*x{= zWBJ*Tx22g|#j6IF*I7&l*J$~~cM*NmfR=?%(04cK_`ub=t+-$ClOu&Od>kHmhs5p7 zQ~R!*R|DTxi=esw^hCT-YjAn1xl<>I5=)coKSaO!WH%ec| z^gcmy4AeuMeq7{#)d`YKQ6YYUWchKr&`*$j4Bv$3F2E0P)Q^hQ&vnFL*pMo2B9;H< z6C{VDF+65KqyLK&BqJ#OfA9p!7jc}zItZq({#x~~{@!`vlGcR(_a2rQyjOdBy%G=J z_}l9j_ZV-lYbV#ixv-_VqSiq@`3uneoneaMhb0~q8UEs7iI3qdM$8oIArn*8{SBK% z7*E(Jj%K*}7kU3Jhb49cm+FwR{0u*Bc@ld@iiB_5@yMz5pSLF|9% zu*CJ^oWFWL640XkKXh2)MLjo}?8=8Fe&9)p8S(yxpG4S)wR12+ZN&&x4ZSLH7Q{MC z{P}sFS2`^5!+pAq$RiS{lv;}7-y>Lurmlf?h)N>r5wFzA6t$kG_HkI^e4d^+V72zT zzIe51f5WwcIsoy$4@*oqNZQdx-GA9(iO=&B@z69h%yW;1r@v^NsQBl@5~m0qsY~!i z8X>|y8YvBpbO4Q13;DbL+Yd|p2)#)3kX>kE=!V}erYN+&{>2OJu*4_5Qf5-hN+Tr} zxbPG0TL&vKCTT;2aadxZi1T3+_|bp8<@gAC_4Eo#A8u!NU?eh@AAs2ehFhjV{08 z@xSXqLBUbjlb^e&KPb>n)X{&yi!UDa=LP#4#$sC;3o?3G5TGvxZaI0GB_TB?MgeU} z=sAwDjvkAb2c~lfn-f7>7-H|b(N_Dkf4zZSZo8F>NxAen*9y1Vi5O5DZ-s}NBps!1 zXZ-sf7CahG`uo`Dr(Zbx|NXFFGJjaGR)1Jv7(L);eFf(0>`Oc>aA5Bq%kimXp7OxM z;`uLb*^MpqZ+lqK6;CJr;==+J-^l%o4+{>^Npt^!hXroodl-Ox9Gk{(PiI~tr-8$G z9a5G@i#ED}X_J`uM5@aiQZ^VVwVnC#tCt?t#;r9&k3#0^_k$l?{RO;#_0Xg6{nZms z(|vx;#G|WmgQpvQh84rmYKsLg^~4L%Zq39C z{1AMneoh=74K&&|(SF4f%Y97O#Sdfs0~>}?!zk>BQF%iTtyp?U5+~o`Ete7~QFr_+ z+EX-V!S=7z5XrNgh{Lt@wT9*^>KT4(7YF7U*`5G{0x2)P1;y0)S=**LPg|HMv2&oh3KOY z&G4fUBBh&B#vKEbmO`H*wI5Q`*Jfzm)#xCz+8<1P$z=>&y{so#~Se9WZ;jw9e}r^dao<2Sy9@Z)UDZT!^K@)UjoeWRyl zo(F#y>wQWoPvM1^2fq8Uw+`CI+eJ@=(af>&jrMi`v}U7m8g5XOHM;j)dI>C-bbiwxxwQeLC&3!jae;8PXozzZ)A zEaehBrqCKS?wF|-W;-0D^yPtR9KwzS`?SNM-%Jv`?4VWQRxTpt5-sk1R^d*cQee&f zv`{Ti=`B=xo(uBS#N!#g%${Mc%yODnmIq#-wRCw{u_I*oRmD3NhJ?#CY9~>JCrFadMt#wz6d0j1EsV-jzU8_EW^qB6Yt)%$&u}#);%>is zDDH9>_}BjMZMc1C+1*)G6)S!sa6%Av7i_t7{PpUvJbnuHkRaSvCj>F`n?KiRs2ZffOW^YIJgp5f*) zhd+s3P-No%CBym(Jf@oJ>Wf2HVBUiAN=M*0Zv z99`beQ0YM5m6oJmgQL%5wSt%P_Bh_aRP5^CU$+30XsYY^n>4SeWPVoez}(z|S?;3j z;_RXY+3q=6^K!Fi`$<_v+4)(WW(kxEONyj|h56Y<(!BiH*^8vYtfH*EVhuX>*D_Bp zo_BM$yT3b0npKdOH?Ks5`=#i@dDzHQIKMc@lY^HxoysZ7&Xe*BW@k$SNd@`s^kAHvn_b{Gr2<&VzJgUjFG&YcG*b(a(rxZzdqIS`h;AUofkH!pu) z-uyhbkme?*ZtWfh*CVe$a`*gvQD{;2Y%LLtSv-FxNy{dsNC7`-{Me!HVH4BSwc;hc zHq#5Uvu~I_>&9#&@oZSC*wAxC=WE>KsMhoIvlgH`l2atQ=ND6y&Qjt`+lE|J=+^&zG=I$0VSK0|g=bNNXS;LYX_`zDueaNu(xHBHQ5NdT74n&`7*6diFEFlsaiDIu z!3BARx!I&1B_!{Mo^I~i#o4n8@@E$(!YuRi(a%s<-W~k>q%j3r&)`%iY2i$XsOhA9 zc5$bYMHnf>pP%8-MvKnN$H>SF6+&PL>S_sdOx|T~VzhcTIj~1nDuy?ip^`MApd^bL zMD)o-_rL|&MOkyn_w`;XEaZ0claO7hd zMQeOPu@|l9*LiWLKoXHpv@zw--3TW5o3XN7zopPi4%1^s2e^UgqVwJud)J-+cxe@R;WlvExxeeAlF+q=!fqh4LYZb|&T z^;1z_9LhVsV%_G?i&7tc_=V@@wtD-fV`C21e!J+_YwMOCREt{gattf^eQ@lZ^*z=d zyQNQjdcTBmFIT)Fy8u|j0{=Pog_EC+ z_%M88e7i$A_jd??HFN3k2QJ)ocGvqi*L8V!=-Yknx;xyO@bo8ty!G;^)MfOu^c8a_ zg~xqa8J!*V>=ski{?g_DwED9aa^G!O&6F@na(!}1t;e@DlDZZ+1-K2k4tNmwGw=kk^*5K)0Bm85 z0VV<$1Ji-`0ds-x1J?pi0Jj1Cjv_xW6nFxde+>DtuQj{@`GG$kM}A;6_BnHbfj=TY zFdeuJxE^>AxZx+{2krm{V3W^=XAFtJ^}uvsPdw#69z)avDz(v5xO;xoL_zQ3sFe+444+BeqXMu--k)e{*Ee!dAqkx&f)xbr-_kfi^ zOE~fadjbyw*8tA~!y}MCOp-o`M1EjFGvo(`G)I2m?Z8T4=N8Bhd>42a7#W59!25tv zcrw(drK7F z_zZ9=aB3&y2fhug0v_s&{J`(=9iJn>%r3|etOQ0y!jFOdfXlifKk&&O$PbL^iTuD9 zfxCe!@Cfi^FXX3iGV(W*q>g=%ANUn;DsX)Y@&i*+Rka%UH~jwE9-w=usvZTtJWN%s z%_ZrR@vs~46fhGQFadT0Mgc2ybZNlFFy4YASI0bXA=Je0qke zE(aE6scJRw0bnig8DKrI4k)#Pe*@jXAv2L5Xq}Dxz?*=}f%gNefxCdUz;@ZNJ1`4q zb)#HhEbvR<5a4xl@J%*g(p*(716~4d13KrSzX1;cPXjal2D`VGq~C6U-GSrtV0U01 za1n4)0oo1tA#fLPQ4!h=IHDNs1}p+b#-JTbU^n0!z^TC0`LG-CcVHDTc>(MOWD8+8 zVEQ814fq()-3ESn6Y34j0?q(VxEb{Z-Vdw>o&eSYKVJ;H0TY(MZn5xxpc~j>De?o0 zfHQ#40ha^YEJJ=^J+Kzoz7+X^r9deT@_}yPo4{0HlUtA~|~j1GfX~ zfp4xr{&-1>xE=X{F+`EqAJFKwIc}7sg58 z=5?w%0k~$ps+It^Y*f{a!0@|ObqCOIld2v9W&%$G9|VRbNK(aSRqYP^0yqI!U5Wg_ zuYenY3HKmBa31gw@KfMvpmHzrw?qF0b_b3IP5>4GOMnjpHv-=W?g08%AwO^u@HFsi zU}$^v=lhT!SO}Z|JPIrUuHJ(Dz?0SR8=&6<@Ec$p@GP(gFtP*e3`_>z0L%n#0xkmX z16BfU4%e;inJ7?||(ef!_i90+#?M0;_;Uz}>+6fk%K1zze|s z*p-axC`nboe!$?zkRLc=E7}d%;&HSa@P;SQ?!Zn@qTPX8fYweJ=YX+5c^mp0a3XLv z@O|J);G<8e>Q-RMc2zwHEPYy4PXb5Qz;2zPKQI}X`z-1WeDgWf8~6>d5*WS{^#*PL z9tM8A3w8r8dkJ<+lBBzLBR??Y732rr^AF?)#=nmIz~DW|4_pg84D7NW`GFQZ=7{Wq z{sK$}_Iwld1}*_E0iFX^0c+lZ-GHZnM}RNA4Z8tt@4#+dC20mQ75F)D2C(_Nup2P< zJ&ZHJoxp=YYn`f|0DcM#=m!1XSJgyd127%<$Oo#L3;Y?l7TD_$@&m_xg#5r&pF(fo zYrx3vlC<)3=nZ`F2=oTF{Q~6z4}Pht4+HD*2=oAO&sQiHxa%8~+XMW!C>J>R7|I3S z^BwF3{0>+N?9u>x0hQyh7jQiAEHLbQl-m>j3`_=o2b>D@`vLX>jynN+0XG7713v{G z0k--P_5xlDjOqnH1NH-c4V(&W`xEj5=K-sL+kv}*r-4U+oqtAt;ElkjWXz9%{eTyM zQ-S?YB0q2iunPDla5vC;3i*MH@C#rSF!ERA2TlPV z0d53d0DcaP>VtXXY2*hE0!{_404@Q(0;~dF0`3O(K7;(g<-iL-`U@2xv75(9Y>|t# z2(UZWF%gejuaxxOB^5&=Zhm$b0fuJ@O(!M1eMvnj<08m4EGTkhu>V5Ga%n*GzU{lk z;?9A4KmJd}|KrHtoFWDWxmH;Q2PzX$0D{CX!T)c-<5ub6ul4X%i2hnHU(cWA;dg^? z0H15lZ<)&zko+U~-@WdVil+9+ztNNb0-{HP#}viGPxA0lu)|RB)6Kj^C`j`AfnN!J zf){^{QT|l$yTFh4;>Q^L67WBOA7tj2ddjZ?ANc+yb+(zeOgHlH20s?OFZ&(=KMK4r z`(6M)5EJh;dCPjE{C?m+Ci#$Kw6ENpQ3A5hRPY~ta7mrwm45=y z??&7b@IfD5QXe(*!`I3ytd@Iq8P!O$4rzSVu@?Lt;JI_^J~E0I!F8wye>3<|<}xh9 zcmmQm781>}O^W>iaKJd^b6}Kl3pX1?|gKvHX zz8ZX6@G<86*L(8Uf^P?&`lQhY1s=X0dL=TG-hrU!G{86lC8S;IIAolKQEaj~p_5WI6bzhv5TW`~wDG4gQ`_FR9OY@!Ji) z7JMT174p3J9D}b1e-ri-2AK7~*`vQ?!@3OoFf(r%$P;cGs+qS;<_V~+PJn*_{NKI!O$Hx;8*^7lRj~x{$j|o3PXyl&{46tXnP%iq z2VV;Qa^pRfp9_8$`0Kp#iyJQSYr+2tKGMvO_msa4{14#!oB5kO{6X+eruyv5C%|_G zk14CCe)*pK0Zz=J!223Y6TwdcKhd1ulHsX8_%-0yc=4+YJ{SCN;OV|?_>ugA;m2#i zua{NzKCk?hM*eN!gRQFikQaZy!5^gj;OBes;)c%KAN+UVw|eoyM~M%>AYWip)h%8; zzZG-;Oa%WE_>o?`xEZ(+mkvHs!S6PkdFm&Nxr$syA=2E0G`{M%4*YN6dwSWZi(#W0 z@Bt20y%Xb_$A+tIR07q3-VwPuP*roxI#^~IHaZFZb?`}E@;eyvT^Of62vSv>*H|JM zV@VQt+7Epd_f=2%FY>X1%Fh5lF%;`fukt-}wnFgtfFJ8se!5ZqI`D6W`EKJH@T0?3 z^+_-Jj~ex>1AiENffxTbgFgwrZG@^iy~?*5<+}o~Zy%|u;a+?bgHHmlG*i`lFZuHf z`5E9RfKN5^=Jp3)0v^jl%$KBZ1@sj_vA-@Lv($=c_jF*3I`Rn z<}}jW9S<8ePSZxDAv;9|VP7Tz?O;wLKV$TRWboZPsOsOn>amH}gW5I|{L_h8pQ0>j zC`Oiamtt8U4GNYQEK<^3vSmS`{6_%dO>;?l_OiLR>T|A7&txjV`kr3UA7!RLVY<@;;F zF9PptuDT68+2?ZiA2*6T2>u}WcypUj+eh(c;X0f|ns@v7_HP%AcN+X-<~mp&H2g0K z{Nlc<`id97%iuG>NA`nnnR(NCrx1J`c!0-7ruEu75Mkh3nR&6~=K9xw4+Zb54eG$R z0`IH;o&-M>{4ukQ6ZaM1H7;GY9aWM-~pcc zi{${X-#QR|uaLh6d=Ky~&G|DuK3E67EBHh+FSbNP`-ATfex#YVh)zg;?+SsBf}iBY zkM-0a{QcmkdGTVwNcl6s9|2$N#fwRn8*zo;I}cFRK4$xqk0yD1bR*IXO;yzoykxv% z=&%F)@8C1cGUQQ48y*7Rc95#R;g$bYBR@UMd<%SMGcRtD+(z__^HcC}6;B(Pe6&0G zH^KYzfeGLbfcNF2CEz~&9B={T6`3GViXSz=(BTdRs?7N!N6no0Z1V0hHFF#xaJ`4Of zbAHQc!v>Y$H-KN_#V_Q%8*#h9{|x>kGf#8Pw}lRr<_OYM4^!258>iXhNh5`!KMu#3 zZMHS}BNP5ea$=FDOPZ>_-B`|R9yw`9a~Ns7^d)^vdr7%SlQcs0x!184{LYc8dc9c} zIn!wOZQy?$b@_IqzIPD(kKm7cjsOjUi|i+{!7OTfPl-d7uJ z1pf{A;a>R%8TogB_nYE7{~_=lz!!Su7lRks|1|i;;C;&2G*eZ>ym*(tOlW z@QDQQo$`H;8EN3Bg7-DA%mKd&yst5(4E)3b-($*F@VA2ZHKrT@A6w{qOlSZ<6uhr7 z#eoTR?iKPUfG-E{YfMQ4|4;C~);&4kzXuQ3^NjhXXQ5@_k6t1FR`935`=bHNu z?Z00u`YP2S00VZ?0^e0jPLH=6s!Qe?3V_sm| zf7k}zAH1*o#zFAm;FHYxO>_1W;N!s$F!QEoVF7OR5%8D0uYr;h@o_WwEA202AkC76 zzUOa+;2#9=9|b?jEZ^c;KNo^;ev|L@^E&XE;ETQTd)Ci2;GYEV)lcc( zWm-QULYmiZ_Pu^S4Ze7>@AY$N4BpLLa{2YM8+vsIKWyoh*N2%%vjJ(6&Gj&?4;O)d z61*=vSAu^5ys!1)F7U^}FEPuvc-Du9!4F+_<@F)G%$zR>2ymYeH*P0*CNd-r1AAEd>ie6>D%{DfFBILn*~bieMjzO z%n<@&@qBBQ@3mDT_~*b6_R8Pi$e#}W<1##7VrKad8TXJ}@YC*4)d;WrAx8eS;3uy2 zUH&%k&w{_*EB~!V{)6C~-KDDKUVNFsp8&tA!uR?(05{ibHux?-5&SmrzShU-;Ja+{ zy*|zb|224D>*KZHe*k}_^>Gc-e6ZQ~_*w`4Ecm5p0;7#hYy6YoCsz7iAG_l5`~v(y zbAHqMI0<~wbPTzf?5PVPYgUtC&?|iNUp9$WV{2K7f!28m#4*Ww`kbe^V z-YevHVM6x(70OQn?|Q*^{W8FJ0Pm}Qh2Ten_f@}j;ETcgs$UKG$}7mP1OL($@}C6% zNKHz<=vt6C={>ByZCxM?2zOT7_)81VM_;uiin|aG1qkj~Fe*wI& zeTsG9-vsZgel_5~0Pm}Qb>LO-zUp@pe6yE)=Xc?uU?1?l>X!t5B6wf*%K*Ow{N?IL zCZXFXtk4gZx6f78nOMZ;|x>g5m6%Gs~sEgI_Ohidh$(r}W7 zdinai={bwgL=V5Fg#&a2-Qq%@hwbSvQsPlM|Jzz6!u;6~V)!#4Ab$=4+@Zl2W0vb^FqREKl&7NJHoRXuSHAqw6H&s_`@p={4Y#H9kgUp!xo{RRxPQH zCZPBW6=2Li@oEVFs|*pS&r6eoMOfE+h9=l4qK)$Z&|W=9G*5a-3s(k-gsK+S^*E`8 z{WSiZ7S`>giT`hh+WU0R@K&X#TZD&cI7!1f8ZOXqm4=%%d{o1o8t&8ZV-1gK_?w1G zYl;7bYZ$L#PYs7@I7!1f8ZOXqm4=%%d{o1o8t&8ZV-1gK_?w1Gj8?ve@f!BjaF~XZ zG@PU10u5JbxJkoDHQcG;J`F$C@R)|bX{fZ(%GWSn!=4%r({Pf8b2MC_;VKO`Y51sy zJ2l*=;l~;t)9^P9l}6*`*$0?Vh_A@778^Xck2?V?vUct+Jvw#m)WzL3scX-qu1UQm z>^Mjko>+7qr#2LSu~3uH2t@kiTz!!Eu1X|mHo8$lEhy6GZ`adH-wWb({;d7adAdU~ ze*2IsGh`pC(NZ2BE=yWNOP5nGk~!i7u(8z*(jqPP4>78>pxyUKtNlJ~s3nb}Ylpp( zt|!wqQxcHgkKRUc#7Bs1HU*1fzc1S&s$J-6q#A0q&BGaxNp$U}QhfVxA;}hpWxC%} z7|a|UTaZA%x8Vnlie~(pVbF7|Z$?S|^024r*dVT_VeReMDAL@C@x-yIIZu;>LC&#R z$Z3PcwxhDy1C;kT25ZMXE%-IPbLFUR&aYd-6pja?dEO{e>+zPP;xI=`y1Dq-DcjR6 zdD2@@OUI5VeqDiA3mvZuakX|_?`=)-&YO^_WdS~5mCj^EitxLED)AmyWca7?L^{JA zUxATFz)a3&frpV-lIvjuXY;@dxOT`}@YLJcDv~Vkk~?6Cbh?|8ZA0Yw7@nQ2{V0D^ zDVPd4h5w;sE>-G6HHn-lcSmcmBXkF0@8NX_#_qzkh1qbgva$hA+-%t2=`|5H4A*|_ z5fmN3%21Oasojtf{L2FbSFpdMgA9!)k}y{Qe??1v3WWuSh0Q`z2hyP}*14KHE>ODp@C8?^z#Wi?R)V^& zSl&twISZZ1l^A>ygiFpKren}i1R~{;xG}pr2i77GCC`N|T}gol5pc^*F$}o6I;p;~ zax#hT&P6B4gP^&qH*fPK`90jIT`5flK%ZoJS_cFMxai?ps(c=;huja0-$`7D*+VH+FnQi$ zy*MWMm7zA_EqGTIvNSFr;MEJAN+fY>}byMD5nx)Bu z;i!y$>|SE;pcNVG1RGmeANZ2Ya$plH3&M2J#@3_43cC?y*cnN8u%px+*&Yb+V^2bW zKWmAU0W1Yh9LVU*njqE{UMB277m#$*>xD8qM09NaIM%Oux@r6-=#{?YWhqrLpX{2w@`k;0l*elRKk?n-!j_hs3 zbYi!ncAeQil$gY>K`pwl=8)W#twnviv3n5HomuhOgMF2NXC7=CH0;HGf>y~a9yRLC zO3?~^SU$?_%N~I}`>|KS_G8^qZVIzN!~U!%at&Zv=+~+26*nIFuxaRCgV+-&YcLxQ zTMc1dk!vVhhFT0`i;!kGOM%QZM(@FnU`-G+lC_M*Sr}|L>XpvsBK>G~BT5;=iczC$ zSW*(++-5!DgJaobwBtDTGVUMa*>uEDVDm8=O=QQ>B9quITu)}xz-F=+P~sH!6Rxji z@%dC!6u=-ve@Hji!<6tG0JNFmz~ z-EL%$pr%DE3-QG)9)4TGra-s(>=-;@0jtGWw~&=!=vc&7AmvT06I$VBb|>0>F`Efn zEn(wub6Cpmfws%oJ%}k~gHZZ%_BC|7g#|#jTiGW_vw}@VE39NRcf5^#205!3b)ef> zHcBaDcR};j>=t;=9V`klYuMAcUd!%7E0nVbG4|idcA$=Tu|v>&9s3zE6>I@&x1Q}r zO*gQk@cE6bIdr?5mBAl2v1a((%-Wy@E7?3qyN6j}+k4sdU2$F?yA!#p*zXd~&|&l~ z$NSk+(0>byL7Hl4hUd@h9P0HTdmEZR#FoP{53{-We1!cB9Uf(0A^tJ;9dd1D6{y$a z?0(33f<1uxKFNNC1-7xX(C{hN2A;E>eG1Dz&DNvtHS9U)@C-|U*FDST!*8Eshu}Xu zSa;-oo_&v)o$L*G<_qj@*l-tn9KGX3_7K|QB__jLUS>&g`1S&ug3njj0{GIajLt** z2V0H$*0Mv;^EGxD)_a|$LGl|+L4EhIc*xnyu7{j`tRpnv&-y}>AJ}l%>I6Fk3;)Q1 zg7DpKb{{_Jond^xhtckEh>=VSedk~}Fn%I*k; z<>X!{ILc{XjWm*c6eUOb@qks9QBagW4>)9hSRpEa2Lj{^C^jmP2VC+!uxC^d4}{8J zp&6orc_30wMxTgs@j#S(*or_C9&pPapjJ_#{GO5^-vBd2g$0K~nm(NlqPHTWuM^khNEkhpUw5Qpr9{`P zO(eb+ZWcY;k6c2M14(?g9}x~InBpGAr8X}~bj_u+YvmajuA*}SM2<|d>O3BBNbOxS zPa#D|(yAln3@RbzZ{XgMGAnPkROp}t!kZa*o;hZMxLvXjS4 z@^*3^HxDrRMmGYj9b_v@)STH;bc};)ZS6qr@9048>x#&2sEsz$<7Q=#JYzlM=XV*ZH2=-4%i)2wYns7Y;XK&7~ z!+6(%y@Hr1_6BTZ|>q}-QV29r9+DIUM(zIWd9>#M)&VI~St{&2P--29#&AW~Gslg%u=95Pit&b(df~8mDf(;1UBY4K zj`I!Ueh@$_WGVVMb1b0zQVVKW?%%iJVib46);FOZ3DGSrju5z{t|T)w+K1xNi-<67NG!9Y>WuG?9ZWPqT7g2sJ$2^M8}Fyr2TEwC^}ArqU?8| zuF>%p;iFRPlPDq~I?3Xo6}C{Oi%6;|(^Z5tWx9!wrc8Gc3b41Nv89Iyx$JXkbn7WX zp+eDKBBUvkEJB(xy$xlG(1wem$5|YGFqYZ>249aJFOo|3F*JNm5Fx7&I#Gli_FS}X z^du1qus=)rCX0~E-i-2PicqMXz9A4jMT8>l_fns~RumUyf1adVXK6uAm0<7F1)-^y z7Su=?QZV)Mk>UOJZqoM#gSMb6Qwue99~n4sK;x7;A0lNS0m@ z;rAv&tvZFRLk_>DXtY)%;-}H|2DC$~^j35o&su%m{Id;^cRrJ-aD@t|j^J=&tN$9&pGnLNr^#11`BUoQN&sfk?RqJ%p9= zfLneR+OXw3kRX!>v0Hc`Nv5|-*{u=JKy0b4@{Nkz;ReP56p-v%$hB+J9%KXKs67f3!NYIzX4-MhUBE~9{o8+mc#53$1wE<@{aDyem@$XnZrD~Xq*gvhCV5<0jYNB7a#;2xXAa1}60?mn2jyEy*{Ap|s!kNV z-QEV>`F%N<4E!|Db4Y5DjZkzMoG;1U#W4XRoa}->xV!N+z9ioU#@(F<9C9>T%iYtz z7Dc<{_870-z5EX%5Go&or@E7QvPgM7ly>*#0k`}fEa>jT0|}B}HS}$5Z#DzvL|5VZ za7;G`eV15%3Oy&L2cLyX@^Cm*jF9G#V~{LHNOQ?8QBI7I7AgC~p_V@ON#TJcc`)1}rauoP%VSW5m;pSHDu+VRm_a;{Ci~%D5i^(vGURl4am)}N$dp@A zU|9H0=s83F05*(C3w;@Z9Qgn$A2X6`U6?)&ohoJ&4@6eWaysrqF{2%|NVrg5LxC~= z7m$9D{4VafF=ImpAh29+(-nbn!Lty!Ri>T6nDIQ%3V9obpO}fkbr4V{zXprNTp#E_ z7g;CIM^}uQ9`PBNO8I%R&x~eA5vZ0QB+F%S?Y7FnRL)E%b*mbAA^ag`R@iYQ+a-@f zqsPqVs@BR6QYGf_z#h2@BUwyN&{o79knbTT&q3Qpbz;A#O=p$|39(gBM`E!rV;GPu zv0YII$r{@o=9C<^;M7k?(`4TRat=x-9c9{z@XL?YwQ%8ydQA+nb6e9#F>xS1M;4ae*X35krq zD8C0H`cbrIYP{-NkJ{VUAtqj8_KrZwz7L~8JQE>@{dh7$77=pUUqhdZcZyJ?{d4$2 zygv)Oh30wok0=zt>_Z_X!M+6Q#|Mc}qP-94=wcD1eUg17Dbj>Rkc!>y(a0U&l-XM& zlq|Jwk6Pu#M==*kwMRqG_-IjtWdD#dwh|$SeKjc)qp8*lzbhlfx6xJORf-oOx2RHE z5lXP9qlx1aL?}sAu!9ICiwY)+P^ze4XHj07s9=()QiiBtHxZg3D%e9*FjG{pr>Njm zQNdoKf-~&5QFW6=2sbrqfCy@h_)sRFg$Y^@ccW*;hcnl!WPsPug1MB2s^i(;K z7hEaRLF{rz&Q!~fV#Jg?1-}jfTjjwRo8+Vb>OM8{Pb8pgAQfFFasB(h1~2S3j`UI% z$6(gw3?MIa${7!0yu@EHS;U3=4Ie-719nXjwmD=sy+Eu#hsUTe&Tggt*S4P}Xb2eA zZzw`6EtC3HMEqDV11xd^n27Jt3T-D^!fC*W*biOX&XmJw#EAHK0sOv> zvb-Cs;y8#Ugl41d}Wwb~l25D9s*$9?2G|o2%_KTMn-6aZV7^ zZ6Q>KWPc9hblVvs1W%nk48bg$Ocf2L6jUp2bUc>mh743K9{z_2lXxChC1)S-3WF0U zVJ+`OyF2Aft^Jg75T%4`u`~48tVF~{Xt6nZ>}E8L64~@tE}>A1jdv+gPTES68xXIw z6oX(CehW`hqItj}`4z%Xl~%F^$^3HQAc!X5q?9 zyt6svNRs+854hw`=v~SyL6jv@eusp<$^&lPq#)LB6Wmq#huj}L^m@(tD#@nKtGq7y zuE_KT&jh{t$Aj7<--~2^FSf?@siyRV$*%;>t^CsDYs5Rh!^cFL9{h$2@hT8x|K@Ct zo1|Pomi^uJ7AQ$};;y2c<#Pnco`+FVIVV#tq`%-CN$E$UzbO~}Nw5@^RWw&pE{X9# z@_W7uC{>O}vepp_<|)bv=6Vt#`!?Kulph(NXi0YZ)`EhKSLzcE`&vj+PKuDrelPl{ za!Q0E?S*92Uzmf0xW%phWn6NvD3v0p5am}UPeRW9J-+(D&-86>gO^cUz=IcRQ)vB|qtgLf>@-zfNvSvvA2ifts^I=4L)@|Bbly zawM%c>g|PO^9_P)u%GCF&_)qDAw?Xa;N7x(9b`tlL3Z0LH{XkZlS+yBiE36UbH|MM zgX(pU98OP}ySbG6wy z+V6u2mAB+5nlriWPm=E(6pF>#8FBB3P=dV_w=?BEIrMAzMiQR^DDTUm-yxJNWu{tE zl_zESJE$^6A(gkuAvCn6Ub_PWpz@U5l!hj$&oC%0$;xB0>mV}PmpVXhm4mvHAKUAx z^v89rRcfCnMCq>UsFJCvQ|Dw$%F}WXWnOLxiNRQ_JS&ovSwjAxR(nna*IGj8?Kb6k z5nN{p8A>D6P7&N_3F%4>wMzsmEg>VxS#e}4^@eIo$P?t#yG3xTC4_#tNqI#CYb+sj zKgIA6_&(IwWeK5ss!}V0wH8cq;iJwtQkbgyI=4!ylinh7Qp;UG8Vz3KP(G4fccRsV zTYfAD?{bq7PLsVp5e@0Er_ca$SPr3t=$6!FKa~SEAQTz$M0t$PqG+l76mL z$PrROwLc<)RgRF=fHPu$j(FGTQRN5~oy{FMl9bx2OS5d>2WNH*{1ct}ky z@|-6eLDf*ck>wMpShoP>Tlq9d`*1SVG2zXUy*1hKJKeXxhhdZk5rS{i`&Y_wS*}IC zKJ=p>sj||`<{FKb8IVE>Cfk})AXO3;?``9qRg#6p`-q@b4#}sg_Z2~hEKMU*r%ppo zt~#zsct0B>S+3#mx!8XMP;HhY!b%GbDSU+mvENvN=|24^MK4CQbuuXNAyOTL6=Q>0 z5QSc+RL$Ul{6h-=6gQ59evC`_f;f6|!#||(eK1w*dP@*JiByRz!G)DnuwJC%UHPzD zyWY{%N$9DKD4leX!oR}6$?jw}N`yB^C+lfsiAIX0JZBfd=>8HCrh zhZJ51GT{xzEBiH(q`4IS3ocotC|kxzE$YFqs0w%|3DtGAgdx~hyinSO7q#Ox=80Sn zV@zUSaz4$Qr=aAd&C>8F)V}>=mLR&@&ZAUDJ4&ukG04RJ$~7+~N;K>&SRgjv!lO4) zw9vT=)w4*{b2~*-o1%(0^h5yH6m@>rFZ;?0mx6gi*pN7rw#Qsmu%Bxfa2ytb}RxKAW(;TazG%E0X>xxRu~ z-Rrp;?-E}NEvz5X6?bQJ-Bq|N08hB6iZ4-E_x@b!#iBHYbn+cE4lWy!$XT) z-YrXs;%49u!8<6$NltnQ>>7ZcnYgv-7R1nh>vsBhnDg{Jwk1V6FQJw#AIDYrc*Mn) zi3)v0Nxdcakow{!HL1-tZN8#>(I_E&8Px0;D6(9nEHq1$T<_qrv z`?>3T1^io_A4EKDFI&gb2TcI2{Pl4siE2r-^G&)+$JLB|De%+hEP?YVE%gQ|JiId| z5_5zK%PD#_Vx{mDR3@UBxgwGG0X>>$v5H|R+JUE5c;G|H_Ztb0QJeD*F2=PR%@X)N zPxK8X;wB~v)}WvNYuA;(Wf}fH(k0@AX`=jbAG5}yTq|8+LI)RcjklxqJM$i#L_8_) z8IvVf0mgxZft=4Uc#oh&^M&h^<{9_a)pet!+1KhE_pB%W7Qcy-I{1k|Y zdZEC?A(^C`V7U=hs1Y>Ty^Muo#%4uph3Cj_#t*SkNw(+?c1d_=1p;d zKOu&{VCTBO9g_9p$($W@xvrLI(avpnY&$(xbd^!0r0Y|3(k|(oA3{7g3~wS=4tjN$ zX`GtvMTz>kvY|njv7BFdIerMdvrBJo)93!o3njRTFOVkTLC`;9xuwYi2)sq4SdtQ?@JfcMl8-y%4`Fox+5Kd~h zIViPtL9Lpn&~sh8!f*f91#8ddDR1IFVeb({3r6dL>hpQZA+(acrxsCzh;68iy}xS} zmU`9&b%^MX*0hi0>o;V9jCWy5dj_8tTNl(Lf>w|A@mfR!B50jxpAkrFLhFKcf8;5& zR*<1MC zhk8;wFO>qHsSx}%TC$Fm46+h6SsO{#p$&p<&=S$Z07`TVALSv55}mzUu=k?b>2INy z=w&U@0ZO!fvmo~hkm#%0BuNam)&-Sk^ZXys(%h$|X-cmb*x%+a5TkPBYM9#C5Bl@* ze?jH>JdA;bl_K>ZEz=oI@FA`AIx77gEfG2r|LX|1SH5izVV}v{xlO9ku)m{QAVPGP zU6e2R9+94A7xb4+`K-Z6Rv-Bs&sT$djCw?UB)`=y;Q4;g^65(5fqYckpEacpbCcXl zBJuv00O=SKIgm&a1EY0871=~fbeERMLYpIYCbl`P3znbHvwTE7)WV$ftAf@AW!x&) z{avKj6?cQPaK!^yGokqbxER+QF&@?Zk2lIGaz(IMk*i+Mg&0pRv5!DqkuL-WbSDbC z3Bk0VL;Z0eMc5XcMSsE@?dXrecY+~z)R=X!qmV-FA@Y|>PQG=;cfe?;DU9}sloWe7 zjm1fKiYoFSA;2Z8%EC<|fbY?y*pn!UWnrXaR>>ks>2D7q(y}ng@&ra2WuQHD4i6_g z=G-VLY4$)`#K;S4B})PZKxLGjZxUD*##$O$Bb=^>6a71BEzwhyPA#U)w(~L8vJle? zzJ=wa33*SKdN#UDeaz$mSV~(VpQus!JTv1lq7X3kT(8|57oiF?4ikec( zM<`Y4Xb-3D1i4~Y>7XS@MZXN`pg}=7t2Cw6wOmnMYDo-2(hG`iGf2^tmt7KuV8l|(a*49#Ptr}&RuvT@c zu+}q5M`5kXQvaEnAeSbHTIyMa4{y-JKVPHiyAt&0m^T}qvP0o>21~^d3*U!%Ug3tb zRAgAbrN!?HiXXj;AXogOwCyy?#f`XIX+lP{RAgGh+ac9{C5UWjsYoNq&E{i{q~t4s z53Zsh-X%4v*1Wr3wS1+csM;=3wR~lWs9KFywF0Gss2bXZt9GMkd8p?9i6;0Ry)Im< zMM|)ym4EW>qU28mNJ7n9&1yEA4mVQapZ!WGgK2e|@#bGqeXFp9ih;m-`=^3yCEB6vg? zJw%i%)=jsx#C%7|QuwV?uBe-438yTx6u#LhSJY2;&`m;_Ev77T#hz)7*~KN2GFRcd zuX4rVY5q5_6cx(UD(}dVl+j8UEgm5Otr#YuNtHz58!1}#cW9|u)g)9u09_?fX-k#2 zROAFuCrea%(Ds<6Vg?RgqE-)70)uZAx%e-<;F48+O19%&Jy7rJS_=p2UA?B%G3!PM z3{t{q*OD5vn>3J(a?ofB>r7Y$vMpRoITi^vnK`2wqIhp|dD-%qyg zq6V1(T;NZ(A;Lp49JA)5C#yET)rJ~MVZAUBQa-Wq{t~=g$ULQ~!>YEEOkS-|YzgE6 z@O|4aG(3M|8!F1k5M>;;wH9Th+33-pa@f|H_WsZXZG3n+Z0j$^n_Z5KmZ@y-?JqO1#(1z@7el_0%u5_cEXR8T3ZN3bk$^R zHe?LyrOT+bb<-*#hJk%zkIPaqTXgXK9>X-VVVa<9w*|huObA+N$lC~c!YJEqJv4d# zziP?!Td`%08~ImqU_4aj*_u(7I~NOa9c6*6>Y8Mvb#=me1&JQ*7fmF1cdcVheqpM!CfnPWj>Ii$%dJZ2TFITv5B&vVq!T zm5r}`(LopU(flD%b-7~4V*fTvg%W%nf=e1gHw%|Q|AnT(pSZFAg#!2}-U`1WjSUH9 zye;tX%_8Sc0q6+tA{8IB$J^p)WFv>UIfAF|NJFuuVuIx^y5mhYY;PG({c?&P&au?d z4eDAw4F7l%eOMV|3*4#|hq(j)V^tUR;>OroiQ;mkP92;`o@Q%C4w|E7Nj0+I>zYEt zG+UyPWjBr8{Y94ZH;J_QMwT1(EdBK?nRHVgWaBqw-j6yXvEmd)JGw~Z3^)4b83?fP z=Os>C(k?fdAuWK922R@uBGFGQg*4{**+Tbneum{DwQPWmKSO~T9Yxs~@q%=-WFTCO zr+0dIg5;!OC9D&5=o41np+DE0)@dkmsE_WuC#+q_5oxYqJJSq#ezbOa3njxNN^ShX z#gEn@^bCcj7ordUWNoX@8AK=l$=Z`TIn63;^yaAYv$Z`vXrWn)g;)2amG8r&x}tig ztXlOl9K|#kp0P&K+jdkv-pl{6iYZ&g4ACPmTDjYD6Cb8g;-8{sxmwK%gr(tlk$v@= z{ZrHoGDVqR=w)KKi=mzQJwnE73q=CQTAHn zL^#97r;>ZEeJQh1&wWOjqS@ZE^2Ihr8p%l+!({ldvO_2mtts-5p-5;yy)rwjy|f|5 zb|?A6^VZI~BE0ACwDzIIM!_$53Z_Zhi>7j^6Nl}j`^Oql?mH!-z_EsVtT)PCW7TG? zhnLy-n6uW}o(jewv`m<_T$IG8th70xthY9&?P8kLib~%v3YRO6E|Z$kx4o5R*1&mM zp|g$3en5rtfo_?#KLx1e&N+~Lxz+Iil2PkBX#@x8NGMW#!y@s@kXpPu`a**f(m^LR@MUP#g% zlJ0aM>QZ9#tth{wv4x=<* zHwWdW`lYndqd`W>A%SKAk@v2aJ4eg?4dn`!9PNc?`bA>YJ*{5kmQy5qxq4~ANsD2o z20u<#qaBsKyc#t*+>Z$7?XC6=N;twLJl7gqxKemvnaS4(cnZyONBKok=wS%DMv2fy zd=bH7N3gAx(n=qqZW|W|yhSI75S)@O(>}h|Ibhtc76*KAl?-g1ecqd5{#_*_Tai44 zge6w>MKRgYCFDUa+;WvHqs#nGx{Pj?Wpvu_B&+5)e?0lrH;3T;4>9%uK6(wwYw9Q6n$~e=?dFzHfAYs{U zwt$~MZTS9?Hv0gm!@sd5v-?u1HCx*v-L+EJ=ZPlvdNS;KD<@?e)@^OG_xD4}MV7~t zyba%gG3|=B+G2X{legiXt!=UQmCGz);tfMB>_N22z1nu>Beo42x3+ncG3I$^A&<9n zH`pX?dh#}0v{hK%Y)b={k;{$|UOkN}c4-Xk?zTmptJuws!hv1!agyhj^0x(IpLAk3 zLu}*}rJJg}Sp0S;eiQ1*%wMqaEOW24x*7r($@9gT%S4i*2Ehfco`QyAn=i8crGH)_lz{Xr`J2Qd+cBPS9Rn%6I z=oOrVx2cxe1U;X_B50xPY?j$1Jx;?SXrX-+Z5Li{J7twGeTCM}^F;T%mI|k9l%Z)) zOm(`?bGkp*D!R{ex^HU@@s|29JCw*Vy0(UROMRG~Dhc=m3ENN3M;>mo&(b8<#qvox z=GTHHr=5h?(JXJw(f7Q0O?}dmc z)ZJ)$$Sl3FP4wSr`iq4wYLg0VG^52r>)HxVMTu)oCxYgnL&pMqE*!qj#OO&d?J&I3 zw+2DrR5=yz(MK=EmY4;K6l@J)V` zWxCO|_uK+_<_vjB4qnt3Ht;OJ9(jn-S)kEg1QUnnm`-OvwB2y1%$SP|54ti-P+#tS zLvN4`GO?>M*wOVn`3Ftkfih7D8f1E?o&_sUS?q-eJHd@I=?-!6CVMQcFNZoc=SLn^ zroU3A1t>nksdOZ|@lYCYyNj)+??Y_br_{k#|3X=i)X=#+DjQ-`bax(AADO4c_IEO{ z+UX&$2XHl0oynLf6Dv`|5kU=C693W=XZH~P*jhbLE?FWvy|Yk=hX-XI2+EY0D%DAL z33x@G#%MYSD*Y@B9)-f=O637>@8kO4+;#W5d&B#}~5|4UW{}-A`{A2(< zhj-1LI>@-6T;CXeE_5` zhbhVu5mozolqxdxp0VLoBIH9~J6P-FJIkDPjAf_VL2r5zqfTS(G) z5w~%GjF1h*^sQ%jyHFB%c=-`eg1%==e_Y3JO7uw=RwkQP(^Hp%H7b^1Oq6Z!U z;YE{qTJ70NV(Azl)XOiSmjrOG528X^HVnQbgLLaZtvj#HAR&YUn-5_Yum zC?C4e@*5JgyQf(o^m`Ytvu78ezzvoUYUyC&8wdz(m3-dWuZAH)Pg{Qd-rw+rHubZP zVq;tE@Oh#W|4bupFGo|@D5luZ&6ZD|=`bd8l~QdIV$ar~TZL}5rBprIK}YUTiZdKV zZ`eC@yN$@{bwlJeC4kxaB5%0M7Ev~P%e$IC$}yNz1qNGenrubBPgiX9Vf^3gCI7sK zpl-p@UTB+5h$?WI*oX$nb>qWMB3k5yUUEd6ux&U|iLO>oc0R~MuUY;sh#zY1L|+TN zB?%YNoJXue(a=s?K#!dL4gYG5G%HWc?0k`CuR9}Qg#x#sPnW8XN6W9Y z`7{zgHdja=lz;GzyU90R8iZh_?MAMExtE{jhfZ@X-y~i3*Dj8MVSIlNU(7Jek~zZb zZnPp@rv0{h$FkP;5%1dA#)s;mT02sj0H;sv>yw>xY-(d?|1-)K`rFjmc|8=GB`(KS zqCHUP2v-OvR9&evHrjl8&47<>=)Hr`B1s@Grsr;<#V%eaUcJP{aVqVFnp~_Qa^#^> z3yh$AQTCyhUt(ad;Eqj7a_=kgDyXv67Z>4;J2A(#h|_hdjs&KC>LJb?m9|POT*s@M zP^BF#{#wg*p9{i5f3vGQ+o#GIy~+N9b`Mp%?$;oVGT7xedaN4pVAY~t1qrThz)boN z!!7kT1`4{i1ufMBZ6}TY57Sjz3Qqw~Iu(`vhZ#hW=lcb7YVbYj_z+$NYj;>nm)ie_>2@+cD7Hp(L zOI^apExarXZ8z$>m|A_pl&i#nO*k$ISjsq?f(&Q*%3N| zz^*XLWJJ+>S|e{O#q(Mn(R`dK7cCxC&G$FOGAe9q<#$LzlTEQm!I7(_CZFOGcC<#m znl4rQt^D(_5I^1v)tM?0`ag`2~|@G$#c3D_NHc>duh45!dsxWXl1Qfody zinS|$vH!`2XB?W;FyCyQDVaBE#SS9Y(`j(t5O8Y2rZ9AwNxC(QrH^q#H@Ie7gUROC zI_ur9s?_1Dm4jzW2ce&Fnog{SUDY39nIA{vU^qHVfphGKl zozxf}L#xSCHQFSD81dslEa_E{3f-=Pu|jiM$&2tPQF3l?r{qSe)!ZQ+ zm6XY|Hb==LRmlcXvQd>(tri7Zb(@1^C3D3S*b_?@_GH>#x zB&gFw6135|QTcTb_@%aCrH0>3j@>XyMFefy=Lnn};Is0PrWb!Zi*0Faurff!`FaHE zSb8JXpJIF}KbA(i5AO6o3NY^IiiB7A)vl$z>FHApGMU8%@1TB@g(5;@aciAE`b zxGUim(%0&#p9VmDe-oRpHPUe#bNv}wqyDBZ)rd}!&Z}nPfwX9*S2+mBz#kwVn$k%_ zY7jM`y%B11 zT6goy#E4T<(AyJUtnWx6ccnfGO8rtIr5@B$zmx=}B1^TD{`2f2@})e(g2=aCE0%ar z#EZP4#5Jm)_(|zLeu8+mOb@~|@wfei#-{7uz~o;o5R-3X2cb=C@mlhbZ9I(8&QO}* zF>L~z)X-q=Kdw!1QCloCT$t7<(`{fGW|)L$*u>m!Z1R`X-`O9kY`hbP&Wj%=n zTl^`t_$Ia8Q)=;zu=qF?jKPDi!-G=K7Us6ovxQ;`Dz#Zl!61BH7?gU6+ldBCdP45M zq|Jmvk$1I_&I5Q1Ma}e@wo*fwTj{l|Za7*|$8$pfzsBx=qGLuQzr>DcpqJOfVtMCp zIBck>bxY7hmmMWdv?1sf(nK5j%QDn8(Z%~T(Iwd?TBS@~6D{-p46(~puxp~dL8+_vX`-t;G?7&|bxkys)Mad-{v zwNP#o-Im)#w`FznBKK&C+$Q=>P?fzNsKa*Nv{{iw~ z3f@nPWPge-k}quw(@Q)4G_@`4w5|{0vw5{a2O_bxnBnL;P5yazFOnJ`&NW8=k6fS> zxfMPlx57tgg*Ufhqw;Sp;Y|NNcuJRFsFkjhN-xw(qg1R!+sip%3U!r2M`@vYDRfk} zP-N&ng`_}Zu4x;y1-zxbFj4rWv9#6(p2wGIUkiE5dMpL%l4fD!#Ft;@dp+)hneFF$ zVNANPeBcYO6jO^FzY=-r^N~akLwc;jfN4M3rL@inc&nOdLHbEG-O>7@kRWk-{0;tL z-@8lcb*MyZk6tYF@AVe?27XLp-y1-SIV@Kq4{H(USom7UzR%b>q=(mY5o%|5Md=Wn zzTu~W6|TGisSFa+4w`itsIyq<*^&+Kzt{1+@j>CzDhy7ff=<2Ac|uBSXAYT#2D``iFs zL?`G$nEg9HH{d5wQ>Y40GQUmA?Zg{ALCG^5Aw-!3TN@gN6h>L$?nG59Ox( z5gph5iCZ_W46sz=%d{^)2ZLkYqLA@RXJD6WzRT}LFQR?}qtKO>f3QR&B=M`eAel~I z&Qr6OPNy=hQf9W6xg{v`a!{r+iy5T=>yQr9F?D-zVj`7k1lKv_%iL4%bc=D<&RQjL-Zc8 zY0U3N7zcFdTKXsAF-my6isc$z-HiOlcM!`Rk5Tz!MET8a!en%QcVa>-*3e3r)Qrh5 zB^*xvN{L=)HskUcViNT$SbEAS0F=sSNJo09*sh`y6I_K4v;>T0_Ln$6tzUtW%uI9y zwSA*dNz5`iKPqOKDrT#5al9m{MnIS4t2@-^mn6;P`~=m74&Z4;MsZ?7ZQ(^m?^8^Q z)VU&!QUvj!2o5afpm^zWQoDP8l1?`#s`aiA*>IO_f%UrQkJc98H@!`d{BDFLffk6e zX?o<>e8HngLlb$f8IW)8Qg&YHcfBAaB~3(d1$4PZzG+W3xh6{GkIiZ3w6qX}gucdj zVJh;IVz~KU>Gx!W$HL{5@L6AG>AAVXwATeZ9}7>W2oJXe?Kx-?hFkNH6r?ZB>B@GH zv#20FF9yj(5r!xELralxsgyZh%U}%P%d~$K2)i^qO@!4&u2eGU&6hOEHQ`RuirOTI zyBc|^d1R)a{A7PEI$4Xti;ux>oL(_U_IP>49N+T*cJ(KJ+WDZuo7ntfqWRe`)&VKB zoOJ%#?RKCt> z)5w*tbJ{e82y)&WU`q)z16?FaA6OyYODaECw3%rc)0s{sdgIGY^lzK&tH5zr?C`VeisO{tTIkFA<9Sevg+@@0%Tbm`du`j~&5 zbN{dLf)S5uP147{F|Ln!scjfO%r28`(()^W4a#2@l;4H-VYqx<^aPAnyG)%_e`@3c zEd|%-3*T;e<^xkkWnr$!M$OlMa;jrK18moFB9PM-iVH((szL(99dvCcsC9?oUN~7x ztA(K2^I$Jkxx-AM2=_vQ<}N*oM#r0_U{5WW?Y{Y_o=j6Wo1pt1vk7CAxlYQsAN|Vq zTtE71Yy+V^M5hC}gK0soR$$yFFB{13{gx{>`5 z#Pqkq(?$S)x{T#!zbPnIWSJ3iq}f^Z&KSU7u4T50;C|PTOD0wIp7*2LGK$Ik8%vx^*Axat`O41Kf)Ygt9 zQyL&j#__+_Gi78=07|m`Z5aVd0#A}+dA5x3BmpN$4qIT$2u>1cl2rL3TQ1d;t@(f=f0#L zMzsBLUp^Q?go07gM);lIsWcyaBNLGl{TJLlzf%`dEK-jI?(~_+8Mv+NKN{>RPo9L< z@}m9WBHbeXA}Uln9))6ENV~{ykPs7OW%o}ol}l#BYi&*90T3{n{1dpTGG575l-@7% z0;N|Y_@}0K#{^>g$3CKT8jZfDbl)r4AKlZAOyq?ijnbG|5n^CAx@811$8x5LP-0A^ zcb)~xv52dwA~m@0USEr6EX6%3y|&P=oq-DAvN1>=SKMU-iU3}b1_??nL+W^!TA-=X z*P+eAMCpG(5giZHbSf{p66^}EMbPU(1fA$tDLOqD1muujA(^S1vLh4}l6K`!Qe z4Mw9#dC|L+^$~(z^vI#pzfiCfwVM~+AG$=#i+l|Gg#`$Dl_L-W;_OAwLpz#Oc`qWU zLC}vH=RhJFf&5OSL?=oT#aO54^|0OOOp1OSpl|_|A=h5>f0PMWc)cHj1I>z3^IS)S zq@4sbuz9@+rf5=}M(wSXO*fs0n3sfJZ$dd5%-L@R+024Vk0JP`0>C%zahcv&LLMss z@)&W-M`>ZyqXJ`E-%4}k$4JJtT$&8?^I@PdW8tfPD~D1UdV|0)ux#alRDXJxq5Fsi zjGdLeiMEK|1n@djU*KN`Bt;(e^4G&OKW5BDh<)=R0$eu-GEON_89!#KMaIT9^C~*a zkC_Q%S$eb1Jcd5XCKmDhAUqoxK-T76}pdaHlB| z>cf|9LcL2W)6)^-ZITyyL`fcVBm-c?vdS((g5$9hymH48Z?YT@KS@<)Xf(o#IrNj3 zCThBDziEIks_ed&^J|fk_zErXRin&DI;!-Y4gps`PH8-0}-KxV*7$WZaGG84&> zXhEKT9W;M8V<h+p(m2BWXx9| zkXZZ{ocx0VLoi^o0Ih zrLlh_-?YC+N`u?OnW0)uAKV_!OmQmb6RO_kOjnBUdQY^}AIwJaI$E95Cs~^_bLpOZ z%aT9eo~i1|zJ^!N#=kGc=F2~?g688pGlOYr;ip+%;Ra0G`DO6Q8^p`+Q%M80h-tsZ zNxC63gzm|@>mc}sOg&izOK6|ABsXLhQj4PPFvx*vglNleK#+elsIM0GYnA=5fPEx_ ziY%xlib;+=)#K{S5@pX_>gvo&WzW6%>dXbyi`lm>1p7;c{Q_m*J79mHV}EI8jAKv3 zz@?dkg+Zg2e=R!GdSNi+e^tNTF<773&oLnV)@K%U)Ng%eg{yu(p&6bl{JJQ=bfD1l zj^DYN!OG9e9|YHKmhSP%yF!|6fUa<5ws>=?a1$h%nZkmDtJO>spK|r1DudKRdVZD+6kHUBCQUPP>{A% zq>WK&lLBdPI%!KYLpn-Zn%Un;qxM*usSmV&1MWCq_DlMIvNDOeg*vg)n&KyJ>Xi7Cy*^0~{sw~gJZ(xwj zWQMN~QG89S>4o}KW=6cq@$d0dm$g{RX2$Zn78~}ogvRUyCtQchKaVHvUYQ!5wDEGD zm*+u7k!abZTFz8*(|)GYvM4h~swv&4C^NmIO^P!EIyOj&BbEj!aebrC%bNoK`K5yA zvb0K@9Z37cN&BT@WJhVgR7~k8&CAqulx8wYEYeJ7YS02BF*JNG()zqDV!jEatuE3Q z_`G72wm=)Vz~>drfma~pU%M;D&`Ok7Ahbf+UBP`FEzoADMZTyQEG_cAw8+0IhDeL- zY2``d%ZffEizkiFw9>o)G2M;i^}`}*m#BF6TcTTYpyH2IjOLwP6(fGZy>q%<6>~`= z@_15cSH)tjGV8yqqM4+?<9Q|WUPX2-K&$g|;+l5|O57@Pul}b9qyq-JO#4%G&`I;u ztrf$i>JN%(ZmpO^lIV2>_WsQ-f3qy!HdjoR{7r2<(%n|kTbvgY>Psr-4R|wMVXD}c ziZY$E!^e1cSCpKC49x$bL28ROCPf-8ie9d$(CHqjd0%4WJX30L>+3@Hjk3mDJtbO$ zGb@IPsp{A^XIAJ)SIhdZmRwo}vL#n{s2`22EfqY{&=cO!n=0gyj{2_+^be)AemN>% zm7{X!T6_~DDnId>sQkYCgV8^M%G}r|R#d7@Ofww##EK!bzrpdOY2)Y&iAoJls;HzW z*MKxWsbZK|9PjeyorJzUU!Ch8A}AS{=ywS<(fo>@js@Fjeg%)VK_kzvm@4^bWD+sI z;&2gx#r0UN2DKYq+)hWkO4r*H&`66bD#-819}h!xiHp{u5q(To#iC)SSA|7(k8n~_ zH7s@-ZWK2xb{b-jKs~hBX^1@nX;|zuT+XNKenTum}-vu*N zxr^gtf#LAzR7DweDYCLHK$lOuX0HuRQNu11?ZpBI2NpnTUg0Fq`!3CcUi-^J=Cm>G zlS{Rd-=%vvGjlV1mmU)IC933i=_!(rjS^MzyL5vz13pB3OWF2ym844SO_$AQrL_`2 zxi}tEZJdZhk+d2Cl{yA0{vvT|r(4l1p4fn%+EJB#SLJ%t=r<_-y3}a-OH!keD%!Lk z(nV|Zx~q{k+v~1I+H9}88fmk=?rMZ)+o)`R7VZG480x*B?m~Nhf5Xjf;l;ze4_x#X zM2CAHy6CNKwg#5_$W<3@`WiaICoX;`KAD9de(I_Z^N?SD=Hf8NKQZ=wo;AlqV7J>v z>)6M?NV|axi}bxP$NlL_YK}QCio&IVIqoNO&~D@Y^qidC#)IkXZsRU3hS8iao;d!J z?y%cPOoxse(&qO&gyM-nKuZ~HLWAjsba%eTd*Lkb;&fjTxTlr7$tCG(I@jV?%^!n< zT$YZmrLxouoZhwg*(WF0I?*jo1ideYz>FTW!2V9y1?6g=v2mgj}EBrroNa zC#T=050-VBY@&XfUO@eYHc@&1Ms3y#zrM8@` zfWgA=_2-1qY+wGH_Q5ITH#j}$R_xj6y?j=1NV-~P&pooV8Cy@FYeJ-tlftT(d zrhZ4>&KvDr(CiIizwKG!dqKdS-&I&pOBACWdm7^3NX-@o*l0(=`2}HclrmTpFbGws zelMiPItFCO7gF;(>i0tGIMok7(9f_ohjXd*RiqgO!53i(~Sq)Z%LI*2V;7wxC(>D z-7bpPkAAH6yqi*Hiwf>9@*#LP3El+g+H^b+@t@w`(~u{}=<#ms=+TVUs(guI@+k%qZ8Y^gDrvruf_y z%@I0Ni>%ZlEaY$}q#<;r=GVJ?(rHnO2Mwsm8ZSB8J05S4Np8JrB2eq zrhO;$;Zb2&iq8(|kZd^`byzCcWRforODz{)mNS)MDVj8(zMQEHOZ{Ga4QDE^qsa$| zw6!V?_TUQ#o?TVifYe~&C(;JE`pb#XfYcn}hZCU}QM$MA8>jqc1pI#Q`1MW=?Z~fp zO2@25cK+U}#*W(ePMxUQd-fy(~3g4`{I~@D2smUGLcTLp`gGMi}|9A|M zqTd*0kUdj52;R=t4W$Nm+*F6M^`&EmaH^-)m(>lYrV6oas>7*6ov(C5Q|70t>DdW< z1=}zb{=S@>_OmBNdyHIsnf8aA@bAlWcJ$wu2RnKi=YA;9+0p+{K2v4~5&lE@;VRtA ztE|O#SETKILZtjl{$V&+RHaqjRnAQjoT=<8=eFQ86_WRExo(p1T!VJ-@0E|vIa7JR zd`!-n$_M2Wf-{x1TFFCIYqKM^6HKa@9ffs=Ndv^Tvo$D5hkq``wTmD^oDmi}~v zaeKL*x#*e79p!^^&Q$I!ADnZhvZXwGrjqy9kywU`O2bs8iGfO0JuuqQ0%cSAa5+P1 zVh`U`eyCJYR*##?7t5guR*yw+@wLM5fyac=nE}5G9KW^Y13I3`tSvv#nT+0RUt2z3 z_|@4zVk|qSJSNdboOAr9oF;R0pC&J_33tCPH+=2`W&JvE1~2h; zE#8eRH^tke)GQwX1uzhL!Bea`ACFTSKKy7l=GPg9dfW`j&%7r z#G$r1vRn^+u<@kcGQV8UyReO;6UYVSeBOoCnWd%PQRO<3*V#Q-Q8c<}y}bg37rAHy z9;z*NhNum_sQRBL>dRi^gmT_%&>Y)KjRXDp3ZLYyEbmUMVP432iSuE|=gLgoqmuuO z(wX*_Ug!bz_QZ2#Luss_36Dm+=gMYClhoQLQTC-W-uz(w8d|J8ZdV?*87=rjS>kXm zJr^zbV_9qtN9seXl-MaAUU8yl%S&BrH{t6p7o({#@s~-M1M!=%S`0D1O#9Q&liTZw zvH_x}oG?F8HdBXU2>xqX;5vyV6Fu+FGE=`*I6M$=I=Z)Vy0dJct1*pIca;qnPPOJM zOc`6sx|197p~iHy)IDX%$Jw5EZ5OQnP?i$&HiXVr5of6gy9WjTRK~{#v?e(Z9p?VB zVyQD~nTWt(Hvcb;Sq{k&?I!V@ZSGd$g>tOrKJJ^0% zEe;>c%-V;AW5mdR)BcNNd}dj1V$6FO;=H=7KN&yhWi2j_UPi6aT9%U5z`mMVV@+9{ z-f}>l?RhAEmUGWKZk)9)S|55`b$C)#z}w*{eXeVq2HT2Wcbk!$LU&*2xT0w#D7tEU`loQTzq&O0=<#J^Bnp_i+o9f-$-e6z5+-^Cfcbbuwf4L# zlfM#MV6>;McV+T8DorQop-Z*cV@`eQY}X{Yv+?JOG-2PFEc*oo$N`S=wj{ap(OhCr z$J}>MvSKWAgXKO$;XfoxNHQI36kiYBwy1=-O1LxGHI4j&dyq%XQSdO|;J~z)-XZC% zHYrf@t;zCQA|Jz4c&$3K6J9l1sX8l_0a?Mnkbwnh!GHLFBeDwVWt#}8ld48Lv?R@Y z4@!xft^7CbOR=G0qqZdbiJ4wo&ED6NtP_{nu{u=vXCXgRi;0gU%TEStJeg==uQ)r| z=U8I4;i49M1zPzWF&iw=0=u1?bSE_}J#STZPpjiv6tjOG?|J7ZE6CDt+ZLM`hf^6B z2Smh=i2u&TceL1B;IQjmd}j-L+J!E@2iO&paAM`+fWL@gRJqv2fgRBajdzKQZ)z=W zg4uSeVwght`faj?evGZ4Sxj1RHvZ!?|J7b`E$)i{B2=zvf0wNJny3$E>e_-M`F}lL z3_;^fQ{srSUZ1Q<^#gGkH|)k1VR_;{EH6yf)Dac2B$r~I)T_PX2a$A-k~=@RI9bz7 zepTcpzQI`wlkZY;SG`M;HMeBRceW}yAc2c7LcxT(ZmFt#X|n7UBEOEA0)FWZ zzWIK&IOV*3$PMV(DWPvGdnK^)Tk#BW8mfGhT6(DRHs>U}7tlMNK;T-PS=51Ih*H>f zwo}1LO!4vRg4KNU`RW21-2lO~y5Lg2`DQhd7m^o6$h8x@vI_PAbw!)shA{QROv`y`m08bKvd^JY{SnPi@j+H?}WpDl9CGoA4?9#Tu41-S4qwT$z3Hm4;qSt2;VEoK9JmBg*b#_exUkUi7vw$?fa1Sxr;XOB6fF4=69_9e7N5iCH&?% z9hiH0f-=7UyhYkd&7Fo0i3oUO5GtOZcH3 zY{&*_F3~ehF-UU>KeD482U(!GgrC@vALo@1$7^)LKK=*cn_Was!I9*Q%JyYf<8v{+A177hHUIAsU*W1;%LX~#MeEogC%9-m zH_(YLO7{5#XaXSN+<3)UVw~{n`%dw=36cl`B2KoLN#v4>0Y9L+JVC zEEk(Yk1r2&u|w^l7{?BBHJi&dJJ{(iRjGGTs>)qx$3#gf8SYIXSqb4C*PI25%#(i(P_qzPH7B2_o z(LJr@nC^?bR6zGd9)ab7?p;hAcQGBUD4FiA4s>@px;9w-YNn%Ekm;_@%gVhvPvzD^ zzmZHw%U-4%*@14PqYJ_0k6F^Em(bNC+LP|R!b zi(Mw?(Nd9xoNG&^Lapdr3ptPWU+FkU`OV?<=SWy+yAoacKVZMmR`dOLd_PReUyrrS z94`M1l_$D6wqhZbA-XxXKizxz8MN64aXvZUqOG2$3|yxDKPbv?Mt=}LlJ4o8sBjIo zPkkN-`?uny=W7uhN$_RbCm@4o`M2T+(LIXr%g}GdkD}g%qYkf-KHkZ1gyo(U!Aa$E zTEOLOl;r2t&&Cg=drS^t>VeP2^$ZYt>;b#{|3Lj~;{3>Q?j2Hm8`9}A?FDE==5kHE zKiz{1PYKt=XUTJbdLB`)iMzw`(2vT4KGvd(SD<&r%VYp<@d^4Ug-v)|d={LFT9@l~oU0p7f}aAvxp5w*9=%QY z=m|UOx7G2R8_#`nXKtKV0X$37tTs13L53O3()6fpZu}7XHi%9)*fHjbWAMu8dliGO z&KFO-N5qRs7%dz;euI#HfQ;Xr@V&eYOi?X5p14^G)dya55QY|Z;p%v~8%pVcVs(6g zxP^F8b$pDNK%NCw$7hj(_~ppZmCBMk9Jq&%8|n>=>$0f{v(HlyZ$^@Xp2%ks#I)(aNM094j6ch*C|fg;L4kWx$W&3zk9Iw7D>y#$3Fs8 z%>A8Mh3p^exqWxWs&rMz?Yk?cXQs7m{dZ#tu|A;lU$4ag5_9LA_H*>s4_&m8G}#>! zO;XRoQJ;^QJvWOIE9D=mc(POC`B+L0?4*l4ALF@&#*ES{VWWp*MbrQnD+6aw(|#iu za1%WotCl9hV}b2Z>|QA`UQ5giM85At-Wv<%^i_*ipg=IEmv(~#T_p^@x=BjT3>fr= zTeIP=iuF>FY`CjpLnVqu!F4!EJ3Yp(|CBPwPVvi#0r}DCu`2gel05tL*l=+iO!0p} z+n+2N+@=gF0}VDh1}Ded%hq1rOeC&|@tXhlS|S~kc-ECz5gV=|z0zGUY#)))swIkp z5(V%bR2V(VTzj;1ofghcAJ0-@nm*d2BV1vcKH8&)idh=*`_h;UE)(g; zD1&Ts>~+#Fi%w7mUjG+hw2Pt?JzNWPm_AC-vS&!U4A9(xL1m6Xqj4`e!hytCDSZuebX2Dg-4q=ioj@G$Lzpss z939o^0}y*naEv9+F%Asfs#4G>eDUmYOthF}VO5LSgE&PombuyE5LmQORNQ`@6w>h- z?ROjqd1xw(maFe^hboNf?9s>y7e;mVXy5_1Fsic$AY`t`An;X$EwEPEXWio(u;*du zt4I~ya~GpmR`VvIUH=i`VSoO$qE#Ow|4n;UtrmMNQqddts2I1)>yeyc|BXn_u>a3U z?y&!MBxl%v$3?NkqpFsZl+<8o^>oDKs}8>s6=0d=PKT!>DWasVPE-BUkv?LmC0^+& zbl&?TMHJtDjg$mCzD)a-wQAb?Bh_kJuk=~?UyGF3s3nF3CEh>@wpL4|mzLmaw?u|Y zw8bH{0L#n*{zerq77vjOjBX*)je1ps5uF zrz?X@z1Uw>kBg*9!vR_%JEmXjN{owS$Mg$PVnHW1TSiM{yXc#)#DY%Q zF&)lwRHwNl!b?0p0VjBcF{33b0__SWQ}vLS*^M-Eweia_^q852X-aUY4_W2#iqy=) z0T?cr*qgNgFWGpepbP9dD2p1g#j1o)<^SVz<^PSfcwwFNuh=qVL$Tm66bnd1 zv3&DgbX5`~=?7&iKo>ufhy_*xRJ{iwCYTWMUo)Hhm!M*?z(>T_Q>>h&C>W4hN%sT{Q82KA1Pv+Zx`UDk5u&gn@gVLYmOglo{($?gRG&Bn zLsiRS`6#@IK1mPNBC7vvt^b}ph%(nI|+11SAHq>s^b`XoKnKSsZOmLyBi z;AGET9@i_sm9H_D@{lO5I4M!17;| zJ|J^9rIQK|Y5L!>cJc?tXzpbdXc{lg_lkai{{Gkm?StZ!#wyU(49sfi*EjshoJUDT z)37bFz029YUmuG5KIP{^M(oB}xc@Ak=neZbgQ2(q_t@u@@p(M6KV^_QGd1I7j3ah; zJa!mmkVzI(MrSYfCS`mP?{X4lP~)xEjG>1iEGohkA7h$Qr6k!*81Hv-90JbW($01DFGZ3cjc?c`)U^GI;PC!^`XCdrr4@cO| zo{yHRvR5GNZf{1|!`_Rqr|k_7=w*i^>}@9@>|+l`*w;RQu-g6=VU2wWVL!VIVSihD zFvcZ28{t4ZAK@T-EW*LI7Y-tZSUQdvYHJY=v$GKnx3kgkBkX*HBki#WN7>U6j<%KP z_G4^+gk$Ykg!|bU2*=s?5RSKBBAj4W&au-F9%?Jl6Xx0)guk+*5ze#I5FTbfLfBxxMR>TK4{k?TLPtE(o{Vt5 zZ9}-gu1C1gUXAc5n})1LTa9p$9fj~{I~Cz#y9418`!>R*_H%^G?2ibS+gA8nlf3}p zG4?8i$J)&ZkF$&6bt~-22#>dI2v4x<5uRv2g%_PP4-GcBe z`!K?__F05y+cV+S=h*WR{>EN`@LYQ{!t-naG+AfqZKU&UFND9f!w~+?K9BGM`zFHm zwh$h5p)EuBd%G1AUHdxV$SFk0$zn?!hpeH`INy942s z_HBe$**nn|SKG_r?d|qPgxA3^9;A6Mh5`?$gDuj2~dl24fw<5gDK8J9NeFNd$_AdCqJ@z4l zf3VLW{G%cUl9JuZbx{(eHGyY_E+%j2kl~nf3_zh{EKZv_>f%!uY1^@ zg76W$2H{qFA;L%P$B?qkeuwZen+Fen+?F7G!d{H$ z`YF*}_V)vo!e6}DayXN{uhQRUX^74{zli^h>V`pd zJ*EaT9={P&2$I22$Ft)akmSWuu&n`7)h7iQ-3dObQ+Z+&@{0&Qs#7|7C*2c#RHusM zlemu&e3SuxR0gbK1kYrE&s0s3XW$+N_(zS1#AhQIb~jRLE#n_mcItqCRF6mp{G&#S zX7P_s;#^aq_(vna$+H!Te?+l7TcP+zw6ZF&6^ef}33)}fg5FpJ{t@Ms+Y0*F1o%f3 ztFjfepa=dDQC8au#Xq9h5L=O7#rQ{SARhQfy^74j&=lOKdn4Nb0g1@GG$9}vKp;`9 zz>6J*al!xtiRxEIX;hi=6`;)bc;QC1;rOExC-xlw#fzQ=vN$Eg2!O}{fv5-^;=iR7 zLLf4LAfgJ#+mRxTn2E%u-I(Bv3_{urSC`~x&G zLj5s-{W&6n^}|sJ`kaBFQ!54QgMiw>i;`MhDLYO`@d2i+>Rx(xVe4 ziEgY`jF5i}pnobs7VUu`Lk=+Q|0opj@M43|$HUWX`5Y>A7!@M6$K@3AVx!Qf!!vC8 z=|t7ys4mZ;Vtu^Wu^9To2iWo(iFC6geJYoEB`Z$M5E&3B#^h}%j zmPmheq~-CB3_GxfU0UoB$W6aRnzY)1SD=iSE<6yiHan0sDM){Y(L21x4kU?cm$U3Z zwTnkJTWfui5hqqSvefjV27KC@`d`47MstS$PR2 zj2hpN4_&msC^QTdbeVSgu|Ph94A=N$$&u)kgx-Nc{BUwK9FFJFi5KF_sZ-&Q(yN?< ziL|HRnL5(}$`Z!o%e4QESTc*A{sF?GNm%ss4-*!R{ANVBr+=E{H!vy);^#7#GNxSmb1+{gF8`(*6`DgTI-n@JCVirL$Ffn;~4`&lGz7YNoWCe?(3-qJCXYHKNY6sKyDH-9u81IsdB~2LvKR{{A^4L;is( z(hDDol}~uDW$RZe4>*OY{Yb!LuN{-aW3Qc_qxMgBY>wJL*@JS_{>jc4dZ@h{y>Yi? zKmBa{zJ6L%0!z^s+Tq=HOpc%Kwhh8k{dBioCM@Bn#aIY_WaFg5P0BLsrz?pi`{_qE zw?RL$L4&f-d}LRO2=&vC>{<~4Kixv3#@p7AjAK;BxWG~$1v1{YlW2%X_maNwwmn#Q zH1c~;;dkuF980}p8)%ZHgHuAxLB4iBSn7zGv=ZiORjbIXpYE4%)q2fN6SY<3YxYnl zlKu2GyHrJb;TN%N48I`N_+p$`KkKJk0-i6}i8C&V##Wp)NN9}EP zPLA4-*)ch4KW2~2QTs7_tW&#WJq_l!*~(q|l=F(9wJ(D_pMP=q4qG^!TYH5UUP5ES z1nEodT838abOBN%&=Dj#IwJ0L;i@g&z{e03e?I|G<+yJIGhh z4rIc!@4z7M^iHY_B&m!I|Ah=MTtMRpn9}6)`dD$RaRHAHhzECSkmPh~#HG!(QwMrA zc;PFl$031?af$NG*8PAk{~ziuGX9UczX8{DGG?ocY@1#4-^lo1Z8lK-fb@d?`)#L;}s`6IDjG?r;4UGQ98*6>ImUpiK0Wb0m&c6ASU==2xxXvDZg_Co^s(qQG0^`#c# z!Q&E0a(oGKX=fdm0-k7-aWp0)fsFBqLhWShzU#lxOIE0XjQ^wVb7%>xGHy~CSzj7i z@&8_Tegx`RurK{Z+l=%+T6t#edB%Ui)2n8Xe;3X#ieX`z>MyJhM|F5W4c!7!0?jNrbdu2rUIST83E47*2V3;W) zf`1PenK&ro3lIW0CGwx8)xl^dqsce3Io?HNuJFcuLPCFzU8%(v2F0&J@%~E}o$meI zO^E0AU%D8v0TQbp%hmaLKca(n1xt@U-3$9pQe(l=B^)mi@+DrBZn_e63E|8d0L}JR zp{E8y-$!;Hy_H9=#dNj0a)Wfw9sy@;xDBQz#6aMc)7WfL;|dlPOEHl zQQ@CeEEY9<^`@~3SdApyZ{CKqGCJzJ7*Rc%3lnss0d^y4{qG^7^-L*^#a|d(bI}^$ zjU5MB6v^308NfH@SPJ-!IhF!|V_+!)a5O-0B$>CVOn5$Drv2A}Oh7p1$OML?@K6r{ zh+~dSpg88p1dL-KlVBVTa2!eIp@XHK^W-1unXGd41dt=~@K5C&k5)MEas&4S5ffaXX|=C9h++Lyjn1DYdA^jGa@ z^~z{`=!F=!NWK>)G)DtCN7D4oL84+aG@#40Z>&NeBpx6caSbBw-~jQtZc9L4{UE+r zYcCZ6q@#>gwLbS*U^?~ahY7^seH z1-u3?>;db@c5<+e2Dpww0Z0H=2*8d)fe%k;!C-*wNbN^oo)mfmMb;bg&nw_M0@_h@ ztj8~$8eluFhZ?|kEId*T)5eN{2UiK;j;>0m3V}NspgU61%lZrC-za<2Ufsi01@Mky zxH_o`@Eyf)wKDJk;L%l8DguPZpsHLEKs-uCHn$ZP(pzb`9?7W)Fdnrcg)`KuxvB%? zv4iTJYoQ!a9w}*0jc|IO*&@CDTBt5Q0GLN*%P@~9GK3Da>0=?nc|?4M#I-6wdZaji zJHwg->QOYux2R!)dNja#B$jVhi&T0=gDxOmWAc)=2iT+IA{GPMV_*;(a=>J&P@wHu z4)7jD&Zf4)Wm=(ggtN9C03Wp~J|XKFAU={;(pNu(tY-lEC~=xr3G&eZ^N~u7R=~n6 z=Hr-N$Yn1D=%eVdQN0wXkD{r<2ZD)eiqFbfQ{X;|rfb>?o0PRfS`^Iy{79P6tJ$I& zkRK%hUs%y6VFu8Tk^poJ0!T8zex$y~AB_+Yq5=4$)V8+pB(?Oi9CHBuQO$wmaN#7B z0sJE+JwrcikIN}#+Dm&Qm+K4sM`w3w82~^^ef486Fqz^kBJ~9Xq}2DKw!%*Of}B>{ zliC0S@-|l9)dnDtl5k&}Pau#6D3DTLynT2KsR|fKN}$Fg7)S#gNQu|^TzvpR2DJ+l z5TpSTB&A&1ODd@b_sT{B%a~l(2kV4@a6KIe@WByfH|IF6IfjT~> zGJu1mXF&u9S%A-}4DcW&ehcEmJp({UiQn4h6Ck7kBBazEZB2-f29S`FaA%vi2QVRn z#%Iq3C}dzA0);d{g>=>-R7e9@NM{`$#()bMSf_*_8x1fU|EGso<_Y-+%lLiWFaXd- ziZEy(Z3_%g8w)r=Q5y|l8|kqh!3O#S+h~B>7-I@JG2u2E05>|@b?@C%gj}ztdIxRb z6Ud$ca^rF&GA0i_5;cHsls3TEuZ4fpa_4F}pP(BJup6ZeK4ABv)E?0z5%5x^5<2k2 zH_;0d;GzNIBJFuD*V>L#PIyNil)UEw$&ikF9>nWE1(|ygfcQdEiT4f!x@ds9NIZ}3 zDz&1|vFS4Hr&l|kfL;6#s>4$@_axvh@}Q3?TbvERi(JuF8+Dck^5Q`}sRLx2AZ*OA zbd}8V>skvh(w_-@Pp3}*^;%E>d+}l}@?fja<0QZre?!fJr2{Vv0KVuJ2Hnf5MBR5; zS*%*1tWV&J2Iz}yP%KHj!tULL>`zK2?F9Hm(RO<)(!&J5Xn?;+Ej*)A3RK8Hn4z)8 zIT8RE>G3J8DF}ek0D*BSGUA|=G3?orE7)ApeMzW&AVKe|@K92F0=DTVw;YK@nB! z6WX8w+#t0Bbn{n{qXTbH;!R!&q4gQS_5DfZ&s3#N`w{(MZTtcq@cLvARadxOiBKQD z3g{JD1He9_CH4f4XMos8t9Kyw`GnYK0NE$`RCCG+w@*ScF#Cw__i3s4Bg)gXe@lW_ z62Wk(hJ2`l_ynz1VXZ2%QVWS7!1jrthQftPfQ>6(q8V`eghQif4&c5(GcQcgat7FP zWZO}y)#F+oDBJy!%UurOayiR=pvx&&*%$#{PMSxalo{a5-GL6kn9I=tAahO!UL62sE=LEz%t;5(O(}5ZR0l6ySqqr} znxg_qmHA3g?@I=_dIM?hQzTorAq3JK^T39XWddoAWx5R^kmk6{xD6qY=5jWKK$>$K z!V6Sv&V~>`bJ-1Hm~iF{faXZVQ{`fr>}D)~pwkjab2*j))SR;nJy^-%sBQ43`^fPkLM*#H81 zE?Gqc*Z_tJdd>iQjx_tMi>QR5k1za~GBlILKEUU$K_NPH;o%ABb5dJ5>jR6$vcbby zAHmNV;Lj1yla%L~0ndL1JOMx_Jhe{&fzDZw5a_^SzVNK`34+c5gHGa&v>^iso!Ga@ z3lj*P0SX;um#Rd{q|3B-8|-QU7`hyZz@bx#Udh|Fpal@!v%2e;6|~RDA+9`-=xB=K zEesDEfTAM?1g9f2HL&QoIUE+90T`W7U=hqaQJ~QYg`&|Jz|kGVJg_a`xg2jbnkl1d1b&H$Fqs8ya(O4I%UtirH#$R7G4l4;||y%E54(oVHL z0n-^E)5$d2P?%9Rw<{Z;z_JXW=|BetbE#p0O@}UuSbgDOC3h7l05+WgIGuBYCXfT2 zPNJ;uUnYSc;OVv{r0R(Bg|+-p*NVWW6Mfb108l4Aoj3YGs1y5PA|-@61Bkjp<@P|p z?bU!AFzQ_Gxn}^RPPo-FESdpIowPUhCj_x$08@818@S#lNF4*5Ix%=d;dqsEmdf$> zko^EuCrk83pMdHNkm`aOmk?5&0i@goB~sy*K*goQT!oSCR#xn_h^yO6XAa^5P(@+IDF9v_xf+dTxWn>Ckg11gj{C; zT_;VnQJV+jEQJ>K34B+de$r^mZIWp(D zcq2X#ApAN506S{L5-nb%#ZCKLR1*Sw07+ME=R88NL%hGl8>Obeu#$`guR;XzvPF;@-ln1HdPj!7SS`@9?gj@^7nfMKX< z0Axo!mC#yz0ZV$(1F=W$6k{x4|8SLwTPPl&MpRq z1ZU?HoE;F#*nBWY3)v5VcH(zyTDmvJMHl+UhpwBksP_rb&H&MlYz~KR@e|{a07yGY z*w*3`%9a79oh0mN@d;+j0Mt$rcD9KB0o6_t_O$q0CV>L5c9O8B)hAdx16(^vxTw|d zL~RLRJ4x8on*UFzd8H~?BKiSXujUxIcIY$YVVe;89{(BGF8?0fJ*(8Np#f}nIZe|rX`9FRf@I187&q|Cg#}|O@9wy=|kcZuVi^nXhJBw)?O90z_=2()0F;D9DUj9%N zJVwc#R{+>94V#80F;f6H&gL7O3o!XrO75x$V7mjd#rbB>>QI_*1lTT0tr{msFCkz%1Bkh9>dr07&a_`2 z^aEt)%pHj5%pE~o=MKctwD5>pnmdA6W8owfk~4Q8KbSkZD8Ah&9o0XP=EmUuB79nU zB>?WES1Q1r0m7a1N=3LcfVh){2U`jkE2jsQlXO#H+(|cmtwlz6fZRzpMQ`C587Oy> z@G*DO<*u9VXeqo#SvEPA+yQ}e7xX6XfB?D+dJ|6oK)Q3i$tR>c1E@PmxTn>hJqZcG zx|4)$tv+Gh8GzkM0$>se?9Kq~P7-#ua<_R{b#dJWXm?*@H>BMLXm?Dtdmk#G-4Rf$ zQUUFbJ6Bm$D&#sB(C(%amGswo?XQi}R~nSqbuFOXol3;gweFFwMO)u|8lJc8!y0IJ z1R0_351=m^qNxK*SNVD$=7e^qw0m4v!FeC9E|<;5Tm8wtw-t!)>-D?~tAPoyAOGO= z0G;%jG1v2?H$W%7W<2+KRtp~E4$63r8=fqC2Mld@)hn32h@7lNPPj8oU24h7>; z$*z}h!aZ&I{ow5XQ8v}ueTE}{94fdU^899Cx6vay+gux>XU6#ICA5hDWAM8eH%*12 zeU+A;noK-64yA8kB79l#{t@_eONqK1mvv%JcAj zc+N;B?#uFQ5}s%8!xKdBWqEFPJhvOs^NxLZ)+G}KXf3rTJechHq7o(4Kp+}TL~2ib zC@uC>o&)5$G#$6kuI9&@K*~K@QS3&IXkblQ##VY)w{PSNBT^Pt|hf##0!#4vO+pYldMN7UA#b+BVAp*T2>=f z68D?8d$Jno?h-!sWHr*uC9E^D8tLs4Hsd=XT8;E^3ENHnVi^06s$G(G!`HRRn(r}a zhE__4$P2CEo0~AZkHOuit};sAHCfXatDaCDQ*F)*{fck4=Xv=jqUF2g=lXh8vgSY{ zZ$lnBvhX}miJY%@Pu857<@vF%JlBCIY22pdPUBw5n%jw7G~Vv3#ux_k{|xfKD0!J` z+&fuAA0UOEVMW*Zp;!54GkQFc|7RcaKFOK_G?JgSY&4Ny zD@<+Z4!&u}m`tvHx{^C(4Cq~7as6){{3JnJwWHP6`Kq}o!}2A={fU2%sl;zUgT|6Q zZVri+TO46~`6TD;$j(`IAfB`A2;#czKpYa>vLlE!7Cx;)a+V#)&n`Ret;5k*!d&Q! zHf2HATZxlPS$2GukYxwWBD`h&JRz$L%p$U9-R%G<)Q3wg=$i;EXXCo|GxF!;!V!w-6p zCku{{OF%E=1xLP1SZ8FxQQ#6b8}1f+RS`D@{FJC!JQ-di-2y`^^_^aMqNXGIB~i03 zON3veq5gP?61l!(k~I$yk@iQ|ceca3sqdVl}E`0}`L3;ar+IJpSa_6q0 zWKAjB^8e}#w0t3Od7f*BL%$8+3WG9-l8I z%!fuyvRNAt?>5nkYrH`Ddx8(od>Ykxf%5kRpPped$1`y&CDv#`IWBuCA-nSCo7((C zW`fbniLx6kjZAg2OMk`1bp`W}MArxeW>+w;CAyY!0+5NZ4}Co$`*^r_e*WS30!_ub zZTHKG9tTc^LCTOv3mPwfFYZPt73#>>D~TSb5LFZ=^_#B))d`?FN2%Oq22}SFRV(w_ z?uCBGH`n496ZvMH3EeDNn0w#GT|%ACt#aQ=l+j1Wp)Zg}V{zhM+{{&?O4Y@HD)a>N zLHq6C8!Tin<&vS|Wd10jO~mP2y)GZj@x1#J=Eh&7^b{h*W!g7jR+bUq{sf;H`ZNOY zG2i_OJ~Z@c9r?5t%xFRB@PA2^i;>-2`;cpvI+;hex_E)iqmN3Q=Sl9;kGTX)i@b|? z+$C%`GI9RZC9M1ZvG*q6bzRqC?!EVLU0h5A34rqiaHdFt1UP6QCd9Uw#ral38pR%kgSlA}9b z7wAFS1$r=Qdp$@yrU#?eGsTDeA_aPo_jx^->W5palJEYx1AJG+1%Cq>l9YW&m^fQKiex_<&4d)uJD~TeN zqrdEXg`4?Q)w*tag`#v`C`vu8|M^|rlRp>HI!f7#-GHquPV57pRZHIV#I@XQ4-xx@ z5|`C=b2I(#^H{$GsFyD%xtiXGyAx@>nm)Kz za!4+!I#UIk;I4WPG+^mn^+_(P*C$6k zSRZnuIH6Y~u(+*0T-J0T*VViBqt0~UzIvU)mYwqR4EcFnIUg6+ySzw`t4-v_dY>l& z43s&ACJsA4^(SuJS0gZlk_m?$?yUFuM28(NtuMe1x7Pb?jXP4VtygSlKq{@*)+f2Q zULTzQV>h|?+u7>xM{96#z0RRI3Af0oTSVhjj+D5%-t|EA8Pd7C-XR?q;Me@r|H^|O z=-u^6F0a?!Dt-bDizKbaPafQ7@$D?j1++nZiG9IvFKrgaSa+AFW+|^`qmAz{}!$uz1M-T3@*ERo- z|LoTJnqMyY>8Wns8gp}*eZfY!&AxCWTxVan5$>~(Hd5&~a>>tZC74`juO*gTXm1sm z+-UEby;gw9mG-XL7wDDtN$#|FyX<$nBU2&^@eph;{eUfYK6@U@Ht2|TvD!ZDe zmuk~xi|0glJX~z=+Rr4?da-?yo9%UuDnAFds{KoT`Ww+4Ty5{?NG#Ku>Sq2%c%<&t~%fFPEs zdv3Y^4&BM_1$xVUl56hWDvu@>x@+#|>vUi>=}P^V{B$j6Zr7O7WvO2VFVV~HL| zJlun=kZUQ1!O2C~c7e{tmf|CR+NW(&N|3n-JIPJhj$%(Jz7zp_|4fYmQTzel?H`7d8Zx*H+d+9^+h$Z?$$YEOqf1(^!%lJ^wgSE8Ke<5< zMf+Ub7cRkt*v{;AH=`!G5!-7Xw@4wxCgk>sb3M8I${wyOqkSEMGn8QX0Ss@9valU$9h54!vu zA%@9AFo!!h?#A{gNu+N)%2O`K)(3y`AKZeEN9+F6XkBi{_G9Mh?br;7B*QYj9y`hX z*xKQBKSz5s$HM*)kzA1N=Ww*n4cV^!Od_o}WGA^ITOZ8%Ir5T1Z$xuMcDB6c+f1#s zUq)11lI@5JNuifyC%GkC=NR#Gtcu|Hcr*vsWcxW1X}u;p$vxRRro)fP8-!nq#&A(~ zIA)n%l%3?JY#sBEAG0u8=f6f{xGLL^S*urNC%G$Iha}Q^m2r~0jP<*Qf8&qb$S*|` z{LB&FnlRupVm|49r;7{@Cea1;HG@5B@zZ6@x ze@+%~qp|<0Cy?n4#!0R))*-jP%H|t4vEL>vjc>fv46Wn6# zc*5Oc%#@z@Pb8Mct{`^Wtl(HvY^?B__N^~&NTcNXiIRzD`sU#kYfb-A=S z9J5R>ElzT4v5v7jgIHlfw9eDf7_Ke$W7a;Uq`|$#IwVo~k5!m(aq)6}&A%1y7uxG@ z*Syh( zhNaxwpIxbL18-N-DfNj;9jmcT-VSIx1Y%Gd9*B24=z3$Jc2&oku1{#YMjy~PqK!J@ zY<`Iw3-|dq?BvG6L)u7G?AA#>ZgXVZ2Hau zqpU>5AM2(sSLj;}Lb!=8S1dIdP%#WbQ!m?F4bE@QX4%OnD@q*@OZ7A^2AZqXSiFs; zy+R+*IG`^#Ya>zd;U2nHSy#<6xvo{g%6!+VV1q;D_uAS8t&~k=NXC6IG``}!V-Pctc^zBXh{U$nzuk3iHc%=1!IPb zzwIXJ(e_4dB+CAbl`Hxn)Zj_$^2MmjlfgR84JRtPU`g34Y6fckl|ey!sXj0$XlyVe zvEnBlrR#r~rwuJma$WgTP5G|;ttN+e{aa0P)w*hxl5HGjq${OuVs+3{joB!c-X zOJSVde0ZpIpFCLFR+U=HpmO)1R3AJ*k9m@4t71~Qt7F=aw2nHm$d9zCbYzAenfTnr z6^YW?jLufFIJOE{C`~R*Ru+LlRw)=#?%pIBGd~U)m`ofY!M*F>Pu^=>$8 z^my8;b}!STsf~sLpAKE?fhv8lwtUmFI8^JRH}9Epy%7zF+wk5kyr1WyARJUTFE3(=n28EXbJW&W9b^X$uS#8*_ z$u`cW59rA%M9p;(Bj*?X5F2Y*nA|YGs`Ns#^kOm{tXZ)}iEIJX(CS%(6y8DP0V_S` z_L@ihp(S@wI*cA<;*`~{fZWg64*kF-?0y5rGv%evayExIayDI_EqTtp>`Mx$vqw*r z&F0sAA5TDe>`5mON^RE#K+9@lyEGdy90;AeQm z7<2&8{L0Xar|iMIW+l8Gf09T%sig0Z>!~PdTyPg=flx+>mL^_#|x>~y?Oy!8HvlB@Ubp3x++m`(s!VA zu=ELza4ed_R;?-B&F~W?kM#BzFsU4WGSB97PvSJL z07|I1R2yarrY`b`xc9(}urn3SF`eiXZK=}&WsdzktlW~E*COQMB}m0=yy)7bwzJM!PH3&7mSgdj=(`>O;}yWc8Y4@?!@) zAJ)_+RJ+$YLF`crN9dGjL=&xrKslYiE^maaSFYBRM+dt8MXIQkE*xKM44>Qdp|Wwf z+7#0=4#fF7)n?<=5bBIR+G5=7`Yc5E)gmqL%L|mPo()`wHF2s$Noq!DdmeAK)q2`> z1@r_W>9phf$>^PV8C~yWbc2&7PB?mjvMVlfycpVTM_wo$_uTyqLfEU)`TjUTG66c$ zW6vhoL16ka6xM}^^YXz1uSAgw$tp0CiM_!pE;LlJ zj~zp&?|kz7iI4WJ8yG?6E;T;Q3B&RK&1y7dtM9qZ<*C zmnfBSnB$V5DuUQET6K(_7CCl80z^`^kly*?rBi@QL$Op~4CfC;Nq%{pN>r$=um_GG zVF8F{Wu608`EAy!eexcga!Ik%gTT>UHc79&8Y&A+? z#|RwftT|S9kLS6^aQgS->K%Em-s!lyUb*nEoV8;V$jLadcz@x;kcZ)S+qfFWNAMK_9>+%Jj1?%<4 zc73~-AM;xIVPTNufHsHp#|%FL?VSFIHvhIZe@;JrMt^9bW$+vPNa|F{FKhFw`r}_~ z^WXDh-hbpr(RcZg`3rueN{Ngmm-1uYLjAEuo165<4t@mt_`x4H;zy@GJEEV)^wSHB zP3vE~N}kalAJHE=BKZ^g<7IxN*=2F6cmpH&7rYWArE*q^=OlTtLAG9(;!Bd8t(QgT zWK~Ye2q(@JS?ha?UblnQjk55% zB(DeS8>Rf5%s(d!F3ZBJQgvA>FH8AV_x6A+$Xt~915z?3rI%&?bt!9?(sPo$9QrH?M^qE*2sr16@Rd|N6ni8M+{hpnBd9F@xSPAR4&c1N%( zGjNZs1z%6pNpWU}Br|s!_^@9V=4^^iS;Snx(jmpOA~^-heJJmcOiqH}1(4J%san6! zlJspU9+l)^P+FI1l=RzDdRnS(e#3!MBW3APsUjo_0yC~v^spi$eNigMW%U)=I44W4 zNKw0FW~E5`fylJ}Zj_=y(r`_hdBHB)=5)!jI}&iJJ7kS^|1DXhFWV*gqHOJy?R35-s~?g3 zu1L)V*_E3Y$g1F%f2?udJP!GvmC$d;yde0s*&2B$*wFZnRJ<+A??~yKRK9**C*oWc zGLH2czf$Q7vON8Ul=mvnRY2j&&fj16(E;Z`mo6549xVT0XW9TnkS{?iY&US4;G9`$=kB^4Ow(WR$h^+ zISCr2;`R4JwUs4;56en}N`lX2z6+@iWjKnUrbg;BProCj-*#ZYXz97G5Tvs7N2K_$ zWVC%tiUzOC{3m7A6oO>cL7Weo-oR zO3~?WN?9;;N#+OZ1|)dHqhxPp{QoroZrY)2$b8_M1aC`GW`mTT^;-fFGQZ>dZ+BgX z%zsOYI}7%@$M#xMFU6Oo;<6M&WWmV+DGrwDhC#-bYGt^CK?xF}lmiXgm4r62X7CqM ztxF@#e_Hl;%R=6!Gv`ERfpM*DYPu%rJCgZ=9{rL*kt;I)rUc)R3SO4NJ#QyPKA;~dm-l_7t_?)cSA(=VHj-zs)!obeQPDsfw zy@0HgN8wH+#+;N5%KXz(eNJlTWEouBF550hQKL?=)TY?2V@GA(d!}e~OP%LFEq#~1 z!Ov9Y3dHt=-1oXXG$%Xfq_|NsugePU0Et{?{ow4C0CES0Jv$|M*M{cOC=>Td()wBE zYYWXcD~rD*L1wJrB~RD>v^@#eRJlD5c~o;%iVzT;`m+iV!2V#Um-To6H(brusJ6+^ za2#X$Y3-SNPPo}7EO}j$SLMO{4Brpl8YV&dtE@;FZWq7$Ll&sMdN<~4&nfh7$sM~9 zC&3*9?NYSpTOLVesPR{2aVQNvI%e8RrKo z&lxm!(5d?>WiicpDk#qjw6^!6EKhY*I^m4+49dYARCf`RW=v{cm8B?wui6an$kGUb z@0o4gIa$FL&nTg6LlppV`YK2{tt307tut{q-4*Q5$i5YWX} zWX0Q3e@RxL+%IXD&+sE~X5l=9Jgqc5F#iLJnbq3if^2+KwqB8j8Cm)nS)N%eMX7a; z(LgciaCGnveN(!L73o3QdP&w_k=A`uz7N^CSV}Ud6`xoP|3K$eRnENIbjU4Pby3Z= zCFiB+mMnWpw(*{^qf-8wRA$ym@h#c-qyq5BjI8_&J6tUD(e^i`JP*e5_kytoFm_y$ z%~zytpDf%bwTxSMMb_Syg-{6!8^yc;30ePj*?5J85LHO#R}?Y`b%oLNG9>K+K&ZHg zZoe!G-jD_bIe2Sxn0NG8gVjzj8yeMljwX*%Hth)k;h2tJz9PVn#M`i2lQp53P$gZ;i zgr3r=+RTezk?Px0dtLTkQPeKMn*XAn!F?B{;)?79lkKvhLq7xgigu~!&=0jBrvt3i znzpID3L5rE$t8Wb5%$tWKBFpb0mjs;ysXuiiw33ow5)wys{fv>JS^3}EUUVt`j=!? zk5mUQT#O$zXu!@3cM& z(%0}WZmS&GkvS|SugNZW_lg%jSSy{fTsLNik&X^ZeE`)FMW2xsZz^CAQ~Gt0i$>xw z{{GA-A@hf^3hBu~=rP!*E77N%E|1PO5?i7#bD^r*LJRwUkM_t(z59iBUHXdLKj&ns zlft*K9-UBQEE6r2#ph6Q7j-sH-|0V<#|Q@eEIy?_Z^i`X+CJ?BlJpmn4l##dx+5#* zRN1c?l(IS5fXr-{Wl#VL=$tlJquFU<+?|#(^x21`?juZnRHxD zQemqOk&#Ei4Nsqdx>3PsccMnM-DjnCPL^Zi4# zy24w8JGxP|+^XOOvd@Ek$l|wUJ}zIUq2@w#3OAxpS3+Rs954e; z-N8d0QvIregDMTK+jve}$037d@n==8mybatovLRR|1|hGp;lQL4#Qd}NlZz=ZJ}*} zre3S$m$!7nH|3{M3CoxO-=Q>vS;VZWzdPi<%+oUe4J9*hjY2?!RPB(Z*xc8p@|UF~ zCkx*%t5J^}<9X9~bFz3)Raiz>;wcT>xj{Lg)1JRAD0Opja%Na!Yp|V?pO>XzJ;K2Q zZmdZmsAN#q=A?yZsTnEhl-iu^whuKqRSadFvf}5>;y}%F5{@)8*u1va%q<;=Ik6~x zUgVlAc}nh^lU10Oqf+`2S?~=Vy1rhv%*p;4Sw17>Uy#K)bAy9_;RNh}ES@s=IQd$D z_+QHw=bTx5MICXdV*cRgrRuz*uVRO&77RY(JR3yZ2Z3r#RzuaW%gc(v@*PSq<#%+` z{9KWf_3z3W-s1ZHnpAyA=UD}ob8aeBkM0CDd{%L^0F{Lv=fe&&YFWy30$uzD(v9OAYqD>$UoI_$ihR1RV!m8JG2r9$B$c!SMz zM|Njkmc5yGq&rhLFD*-gkG#;RQl$1_bDw$`BQh{68!{*CRW-7EB~cIP zuw9m)mL*7;oUBW2sU++q8-rzdDoCM~w{7=1SL?-R7Dye`Ym zsmu$2y+ng2acY-3N zsopOo(^9%i=A+L5Cpeu${=p&OcgrIfUpqK!%-koX>=cKFK?JNGUn-rsAtm_o zApN7}wdR~^Xm@L|rw%E#Ae=TyNfchTECR2sg0HS4ZkH-_*onowyhKMrU)L&Jb72Q z;+Gsbs(XZyXXiH~wU{24i0^0^spyg{#m-|#t9S%GrM6dlz4Bn@C?bbA$$|{>pI`%q zY?__O?VGarX|*<&beJ4Xz9vi0OAW?W2ity8W2Rd;H#J<(tEE~2Q+z^7ZhlpkUzFn1 zmZ}Vq!zZQqrYyTCYg6kNtJQ9*i}gy+%PtL;z_DMEU0BLjWHSc+N_ehO7X6B>LxWtB zeV3%PjoEj-EvjG*X#b zOVkZ;uPQ*rsrA)Tm)cyd!!{ySmLppmrRs~S9_ud2gO{XOm#%-?#ZcY~ImesHS7Ge#v$*>mfNx@vxwC$0uJ;*aFO{}1>?vJxs-WPky4vlTdl`g zrc%2pb3lq;lZC1GFA_;@s?v95!T1F>uxdc+&uBb?h@Tpa*JZ(V3BD?eGmj`#8eX5D zz6HI+TZ;=?i+l^~0waFG37CecK8Sq8bDkMpY4EiyeNNV=wpYp$gyT(3S_mH2D^he( z_NBHjaXLLG+jTANW&tBUC{l1EN(t|;hCZ07=#^I>?)}OHDAe_I2tFgrbX-n~-gK`B z82f4IHxEnk!TLQjKj){Ynhkr^6!3BTSK_>)M zgO|7Qggi1THTXwJrK_^{QE*>(#|{?b&0Lj-W$lM#5$4RpJSL~`vPS3e!FV1|!TBGK25ERCvM*meqOBUCV^jVA? zT!8d9qJfywWO~wP)bm8xX*_XI_V1Kx9}AI%L_^5UbZC?pA})DDJM7to;Jzss#~c_6 zdG#wAPEt$yj#}WwD&{PHBps=3xD&*loWvWHE-d_Nthn=1uA&Ej?dn>W4mRS7Q1{e#kg>aEpC$W27oyU4P2i(uFd3!T-S+FkAp zK!j;fe8Yk`+ei;=ydleP5|F6E48kUYkgzkFlm-w;U>SjcWcZTW$aPqmG}SE~S62mN z6bz{a@BkvpjuQuK-5FU8avEhFKQBo+tX!EP;CEG%QO^G%%*^ad&S;lyH)Io&W>!mb zowQ*6(YO5ibt(Fi>_hhDWCsa1Qg6B&Tr=WvMX83M23iTnl1yoNgA~8LL&|?ks_{{k!iW~@FLtZH+MR$W zc>I)<&z+M}_!%F8%-9_{T>p*~e^b^Ul?qaF9dF3OMpY||1~hI?rfCV1<)%h09jL`$ zQQN4&&8mSiGKmX2rRWXB(P3EzvTkAlJ&VEgkiLXkxvlzwJP57>PLmUXD{$;ry{PKn zROSt}ABo>W&=@W3fYV~v>~|HyQ*k{gd+RZB=9B)+bV>S#^M~i8QJ1*o$PIVvqDHB< zcdA9w%tAy5ie?ZrTPkD^>A%;c=rwi0Q(Kapd0E{>HGJd}WUx_X(Hd0-B&NuCR7z{+ zQ4RA|sG}~G zH(+T>1biu5#ftcb{lpaEq<&A-U{QkVVCx0lM-|o*5lVi(E2~kkw`CPl=(}E4l8!i>xp=)^y-5zCEV%yTH>5O+ zmfqa>2zyynBYW^9a0ywq7GZojM1}cddW5R0g3Ja0BL?(wI=+lmKsdEi(Hd|bIP&|XQn$&GIX63DGNc{WCbPh|uZF&t)#8 zHe;W_EoGlm(T)m0LKlJf`v}wV3nfs<gsPxeIa?*N=TB`S`}gZ4Z2pRHs%{Etf7hiC8_NS!q@Hzf|{5qjcj`m%7O~r z8~S$;Qb8s=$cSJsQ$GWY6QEFG5oCTW!fR$T`i=+};*@d-)F2Uk?SfO~h6mrnxLuXn zf-wCWwjLHo#w83mz!I$L;51uJr|!e2C5;5Ve)fkKe!Z5R}b) zSJ%r&#Wh>2DHQ;^k2k5z&*ou%pDg>9^#+DAU`WsE;>$Dt6k2#i)@QyFf4K-Tm)cng zIR=^8)Hby+@HNg+r;|*cTu!MLC1ZosI^DBmOfSBQ%Eh#F%*w-xKSiaKE3nf0*^*lO zmDK94loocSv~(+_l~;ilT?HtW;?y@jtuxWOS(%gvti)u^ZmGQ{_gzC4{3NwY@I-Pf z_&3QqS+Av0^|Ir-JgOU^!;W17mZ`zmPh{_eH0)8!q+9j#mO8DND!w3Pup8NNq*$<- z5&MI3-mvx2{Tg$MerZ2mmei?^UWbH1l8tHDQa@m`6G$2KO=_en8aeV=yy@$M(9}_f z);fztq|$Gh2bv-}G3Z@OR@5#`roW<=hAvVUTyGx!HTE_sz^1@b$E08P*;VJCu*s@AOp7wC9UF}re@ZF_3Ff-pR?5OO5rgW-rQ$3PQ}^NPJWfa! z+;6)SRM#<%n4pObf=WjyV3J7sNzstTL#kdI%W5?ei;8GTpqYjgq(|cuQ2SM6; zP^$$G$-xFKlA|u`QIR{uMmPlodl^o_;BNEDiLSyXC0H1vvW(!&IcdmgEkgrM0@sb| zn{B%+8#qRS*kc;^hAK2COgMd2yM~vSyeg}RJ-)4~@mPmEc|il!+i~tL5N9PFctH)g zy`36IhahTsRQv^XDJMibXIJ}C<$?rbNd{9=9UNwwVI-9%B z>2$hqR_vdGBiEg(uEjf~@|r9k)knk;2}G-jz2JzHKd;2HxDI89Ouvf zBd5$5Q7+{`0<@VIcmDtmd^gX5cZ{YrDi(1dX(ajj|6I&QE(P1?>oR{1(*=_uCykxz zL@BFfsywf~;dl+`q4TScdR}`;>M;7MZ^-TovgCqPrM6aU4ukytLQ<;qBtSS9%pa9i zYVBQ?b>veBE$>HQX~jVb?{7((j3q8c#~vwFM#LR}STux|?AR-1-z7h(X&8P|7*O2E z%)F)ora0F@FI?G7Rj?7R%bZg)Jw=8a>+5ORh($~h2l?DFsm1Rie={aK+U3CkIoK|H z8)e51o$$drZ7$g-YmZ8&($p<^P=C$I{W=>1^;f&BxvG^42!iArAF#FjVicUrO1X|0 zwLk@mt3+Es+aV9$k(#$;4=L6;>C&fHT&{c0)(9tW)coAGM%mv;{2f7!5X4lp9xROrr0Ng+r^J?%>Ff5sG%2GmVZ@wuN zZ>lSDfU4$Olv!7q~WX+9l3z8yaP<#ku+w&O!ng9^EXc^&9RE0r;0)*36#N5NZ{cZs^W|)6XVSew z|1yi6O7LZpX%Fb`g69Z|@&Sq`D0xfPc4Tri310AXUaE^K{bU-$2mi$O$)sC?fA?hS zP?9ZfW^Sge4gP#xjrwWqfBR9SoB~x9eB&9i!zCUGKf9*J;Ca1pxBqf=dkiu~e8Vz~Lwwm?=56|ba zsXFj8r&27!Pb&C#5q@T3_*ro4uvC3XRtF^49%^SlOM_o}@-60E9ejN~6MrsM?zqLS|&tJTW40A4F|p^SI6I zVcH8sWd`K|c80=IXmM}{A}j@E*Mi##DkE^mDv8Ffqg;ka&F`sw!49eJCvwR+v>omE(MDQe3xsrXh-aq593M4ixRslAIb&-hL|C^}JveMd4s zp%rzS$D?zGNEpA$_(i6CkCIa|wWDemPc-qp-9BOH6Z&YNGbMm@*5E`kb6d@L(P9(? zZhH2PeV|e%txgmsX5$C-3o&?d)$j`F0yxb>q5*icLqmPZqj*ZB4A8&lW##KyMn{?? z`GykHPReH>AFDaeQKcZN`ZUafy$`8}PS`H*gdM;$n1%Lkp2Pag+__GQ%l^BG-C0ep zf-g8-k!7TA_*M!Px|D80NrW;%{naJ~LMaCg>K9V%;TWkAS@0qQhbc!`&kP{#(#9o* zsTQ^&@8(Bn2UCSPQk2VSgrPmuu8t%2NstX{B$dN)xD_cNk1ufV)f5{`IH58M76yam zb=dlMWF?yHi_E;4gTo;p|GoypgCZJT0XF690@%na>47m?+@V?X zvNsjO3rJ`~Pj}3O2Cc7Z?H4pG80I_41ruS!_q?cu2=K&wCLpoG_sg)OR=r9>zE+;T zVhK`TFMdl_r~gp>>Pj=_ap_yn$-|c{S3V}&NheU849y^%HbN?YPs{H~3Dl zhU6frPA#wCJ7?8Ocq%)LcKTpKiH? z(wtcXp}oscvv<3-2k6mXbJj86L+xE>^){Y&G8iOvgr6|xlIIJp?C)#JukDb1R8YR9 zoLbi?kE)?Ad*F^aF#xXI3xLt9(*7m;HrNdW@Cgm0tcPBRdd%wQ4Sa5wt(0DqeN*u1 z1#moqV)M_oc0>(K&-cRoLp_5=*^8$6L7o8;;HvCHR=lMoOCqN{^T!Gvk(_G$k%a*^ z>m~qxOEpS4)g2TG`Lqg?pHa_jAwmj^ZZjpiw`2$3|HI95N3sJ>jv5%($@6N#!$V|c zwTC$hWhC-f7qq;nLUcTsKB}_2PhVegF-_c5S{yX?<@bBFV^(W+eHgmFL&dDk$(|I5 zNnF*2DxYA$kk{EUz-Qe89JD@gsXW%n=yI6X?iW)d!1P%X6XL&VD5V6+a|JHUGO|cj zXj-5u3-}tKb7WSK!)BL=$y7tN?B_Rrfqul|VC@F|h?rcR+OtURGg?B-tig4`QN67{ zIfh#EWR~4}Na_x29lhWFqc}ehncJT|!&(9*gft@0iZb@}XG5@XDzJXGHAvNSvhBQ7 zP0Kb+J4&&bB1mmn=#bnloBpm+uUQi0D?7A8-YhxFUwd?h$C%;gWUI}vwQvUGcwC!+ zIC90$%9is|@nP9=PAaHkO>JKGSGGk$cYN2uP7HT$_hG36^N);3)knO$hwePg3GKOu z#WQPurnhpa8w@R;ZPk0t-XX$n_Riva+BbeFvo=?eoy z!=BL-KBxgj)Zgr;%xv_AZ?gU%S&yVYftiCTf^?scK1Sg8QITa|#NbiA3H+vZ)wCw? z(O>g5`)OeSST%WWFW-C0>|hea2U3041$m@hvkeslmNABu=tlWkNd+bYVIU}E(*>;) zuEScU7K;eb#>{_K*=i;=`i?r5s3D!$V|SYLOD|Ryg-uVi4?U@~UbA4+IW$s?-WHOwX=#`KAdnLH`Z;rt;ULeAAaT-}L+Xf{pYi&lu^BaOszhCnLCBa+agG=^cQShnz zQuot=FV<=-c|-86Bcs;wqi3zH7EbWzMQ-rgV)aaC?HfSPZL-660#^6hW zIUAtKjb!j)0ukyU@YM)eca_(Rf}i|QYFkpj(7jgYT^GC)=sNbC^~2{CHvGE*--&r$ z*6+VZ)*!%ZQfGAO>kOCR-!IJ6FkfwOX%dg?3kJ!*K9kX`hb7>HU(7hze|2r@K20lZ z2>$u|uaFqjg$PpFfLmwsd%f!W|I0J?*y|VlUjM}zMcQ46_!a;14{SCwV}mc+Y>R^b z?SotuRj|t-cy*rcQnMg0>J<@d&lxO#vG5X5t_?miIY*oOXU=e6z}Hyom*=Gpkbi%}B%#0xj*=h7126UD)z;4673C=lYc+U%*| z*O|sVLtlMInEuUx0%IXojRC#!)mNp05ZDDdNE+%Ix8sxq-+5YR-x7SHmZvAer{DHZ zKUsT$yK@!=pU9@xbJiDxw)XxHHSIj-vZkl`;z$YK@Uw@tHTLk=pVH3UTw+gukKE6u z6D}=7sOBO`DZP(pQSc`Nn%MGFZVFx^va#@Gd-J`4)E>WDKIxJSeqvLUl=6NFXb%>W z+_+aF!Y8G4*KBl4@P|X5zCOwKs4B)43zm#hV5)*YeZM86+(uqns~cGqeDCSPUH?tI zsyvQvdK~?R$I-iY_5R6PhokSlKZc{9vZf@hv~%+IvU*PJYBEVBb3S9h?|U{eufI7?~~mX*yyF_YP; z#ZbkmotobrBTBVxQRZj-h`&^RH)#Cp%GlQHw<7*~6R^SW@ICv#`V8iNm%-%99+$y9 zCyV$JZRU*ZBtW=@FG9H#CSSVc+fJHHxnU{HJ1&JeD^K5wr!c9ASe?$iPowIZ#H3pP zh9xoiE*xL@y(rtMete7gJrT#-vhnLu{;#Cwwv>O3B<60Cm?Xt3`8wb&(JUt4(xIf` zs^w)DeT^*Uqhv8jg5J<9=52D2_mg?$9wgG3x2RmGl{H+DM3nWbx_VjW0~)RT=X`K^ z%wT@q|Fa3qU=!Q_QPMHYdj2Tss6q4}B^|MkqkQF$l8*o1NIKp_5vSHuL?3D5op+@j zO%GGOWO`Whj_=jOKRoXk`~oG*|6Mt=dFkNehj2jxs<+ECFX(-G)S*$vAN@=o4Ja89 zZdv;;T8goma1xHo!xY`qkMHOQlmafGdZu{XrPKK`*HIBJeWBFe<=3eY=1UelITQ8Z zJEbq`eJMnasU4FiQTBWT^{4F--Kn8?QcI4g=_+}@-e>5$QvUmts#(grcwRN~G#>+|Chpp`dHu|P{LoA0 zmS?`G-)c#|Yu_|+#ctuVjDG#ZwCYw0-=e33`qY)fHSm-=e8y+&dnc>zF4=<5SsFQ>U`SQxm67j^xItCKK80(8=+!$wXUsZ(G-6+3ud~6TNNy zt%+09Q)AO3Gc(!TiRqD{Hw`3a zMox|l=SGfaM^Ccp*~wfYH<6tg8BUCy$_`DBOb$8NJQ|x0wq_3=>9UFX?9GvACf#Cl zwzCNw^&RN};r)rJ*<5z&%;d;)c4qvAk;KgGv5E0qZhUeqF*KYTKRuFF#LXnw$?&P? z9bkRg-qxN@18wN|@#$TO$GVQxG&k(3sozuAP*-2mu(M(B&W4>2)@+~1%}$Q&9~)u% z@nM=n)59nBpM7BO_Px7nwvW|p@7|f1I5jbxJvBRXBEjyC4b6;jn6u-Pxd*bj#HllU zJRU=LUu##(;l9Tb*#lEkC*7|52eVyGhg%JrY+HBs*z9O_WHLVJ;A5@*15N$?y$<@; zPLQ0)&YYN<&e1tBdS-e&$Eu*Fuj%pDmTX&#L)!r!^cy|tA!OV7PR&jZ15_bRIVgIY znp?ANU58pByTs(knb8=~y{*lUE6_8iPL2g%%&PK-=UO+TNVnm*2XO+yxnFCziv1`ZC!V3Fq&L%G~^HuwCg zk?h3K%(DqNW_Fr0&CWbK4&%U8GtZ4=PjIq{f+>fFpPL<@W=f6~(g!8*LK|D_>uGIn zJJ{BoI5`CGIA+w>o_F0Z2jk?(*_;jSY<;}7b62*xsre8Sv>k0t%$%IcK}yrZaL4pa zF2cykp|P15Mw*(NTYLHw!$T+GPRLMgbfZ&=v5_24M@D?-#Hpd&2|kaX)MFW+95)6XpFzxM*HdR8;_P&61D$Ps{jFUI z&OsN@)3L)D<~%27PkCV#>wmbZt zGwl8NP;O}N?%1l`J*{0${SA5;$4)+Lf<&i2&;-!=b8FS5vd#TSy%4II+!Qe7CQdnu z=|(UefP)SqO@zAS$^$@7caj zogB)IPEA8GC!U`fhlT+X6%=83m;*!H5?JW`F)np#?vA}3FdPd74lhD21mPJ}+3(0M$sjH@Z3n#&0w9oUe?a_I{-ctf`5KwlQ=*WA(5-PVQDq34(~ zP%besbT)z~4|X4<*NWtunjA%pBV^F_#}g-~&cJP>&t*@Ij69oYLjp}t44K`-u^s5_ z(9`Se>PYmPk<&KSJT;js5W>5?5Jru3Hya+2B5DOFz1$_}oGmnVa_ShQt5Q$*Wum-3 z+va4so5qbBojrLnF$&wo1$f_Jmp}4e7Kw|1gMFbc2A}LxQuEW+f>5Ry3(6{m)# zhbG+i8Ze3uv~{&8BAXBOdfK1K_f<(f8JFM(kMy?=WS?ki>&H|CK|Q^Ch}a?!Xlu(6 z2=g(W>_}Hfm#TIwf>2h$p3Z2?<}g7a2~`fcgu=!Lw{M*>BqTGxvfeE7s_md6I59Ik zJ(L?h;U$6DQKox5zhsqdhLu9;G&IdhU`6$rlkvupY8AB|U@v`67O38L^l`Mcx7)LZ zT2rG_rrVtLnWxvT?jF^_3a{eO3}+U@zKPK6AlK>pMgV_y=H$qTllFashdaA5)s+bI zQ|2+NCfBi(L-6kGBz!YDa=gYi;s|eSdg=^X*V{V#zNtHe34#b66lhJZ>Q&)3qCl>0|5LOF@6arYS{QNeD zpHqAfO@eU6MqlgUY!^SAeA6dK`VRH>8yIK{@3&+}P9u{NCVLdiHnJZfd)PW^)7C%K z+uhZDqz?cSUiK)tWG9fq<4hH@u$lyBt6)>a5NgDtbE))h?RBnEZ&uYD_5shEdyc4M zgQm-=q*J;?A?8l_b5$Wm=O+9q?d>f>U~zM1TaJ9cGwfhQp>9^wQ-`5~x|%v!9-*pM z>M_N?k+WZbW&))KDa_=KL&G}U6!PBPs*jPuCTN^yw6|T}y6o;n&Ubembmp`fr+-M17_cv&26S8q?b6JwNbFw&`C^QTSN)=@utorPM=~bcQvu2!-v~YElo!U zyl7?jnNveBP$WB)TF#m) z6Phv8lc5*tJr5PUq34W0feP{55K2c>Qvw43IXH!CF^0QKu+NM@^x1>T+x2@RUcwBY zf*_3Y@`s7Qb41_-V&eGt=%|lSOdLy`I1_21iP_1Cp;H!V7)8~^q74bO-iU5LFY9~S z+HfBGTYI}&LB(UeP0mQ_LyKTGc^XsvoIO51h7L2Oriui^a%B3nW2xSzC$i3d=yOiB zw@UFCv4_14b!Z&ptselU%>qFL4h@gzoL_(V2uBpH#?+^wl<5(?lF7uV1z!9qs07b? z)rpg=R=&wAvrC^-8y&=bKet0|{n6u7r;vB!xIS3oM#qjy`jHDZ)x=0{C>GlQFHdy$ zc7#?k4*g@j*Uz>gA%!+ajfIq(6rOL@v~N~h*pLV zCeQ=eTc%S~pv??nhMPq5c+>@9jMGGYpaVw+qZmuayrjU1g&3#fa{EY6tBV6D6Pml~ z)VDJe4-(91A6Kdp`oIBXxOVmlVg!=7g zoP&IF93ME0m>_aZK6fg=sS+@MAluT^-(;R*7U(tNroVP2#?Tz*79$7Qoj0F|kvnR?Nh@4U@T}8MuE{>}%(hoY_3FWyM^QhvA?+aYRUp<#DX|@$$c8Qo0%aRn)i~S6il1xlZi*$L;D#T8Ik5@hAVRGHwhx8hbQv!W7XQqdY=pekA zsY!w~UOOE@K^h=2cGDRDhEI*-g!)e}`A@xVpJH(byRcI6mlj{;*YxZ^@IMUZ|Zf+Fj)_{aYzmO`~ zQG!~t(>vH-YiE&qJ+>$pNn7lR7{pRTl;aG!<0of3jn~Pex(LU zCXW#yv>45dk%%3F;`I6BLt#FG*z#SZQ4sQ1mD}CdMjX8#+BJ2o{zQ~ki6?>b)YVH? z=J9+W)jM7q4~zLU8j-Phx~d~Djx@0lDaabO;=BT5b0qQ@1b)KkbUI5w?-W@kX7piG z#d>JDnxE?wM5r2^)(PWSc~~vnj-jXNF(=jz;wl_+$*vU{n4DR+;gPyjFMwup5 zNyJg0)y8m1y+f)>HbRx=Va%?y-MY}H=YIsEsuS{uZZ$`yQ8prfHhn#vV#u6AYZIDoV0#gI3Cqi{xwmAc<-xvMLRIpo-X!BX;kOI(@7HW<;ZAY{tcL50gcLof0R;$4(Fm zFalOHsY%sw+f=VE4$f@nI?94G-(MJ})F+xSZEnJ24M{>{f?)?t)W=cjE?Ykf0ySSygU&ie0E=jKpRu6r z$?KesR-Ycg?a8VWs%x0p7RIw9tIgree4I%&|6MXr1%eGC+6}GtD_G-GSdcjOI#u^$ zAU%>^DtAtiR`!kt9gK|bQn`ISO~i9m0z2gs>XySXYR|{GF*HbhyCXI#x(6#mLZ*<_ zqz$VbT-+Z~=gnCdL}iGkaI%;wlT#Wlb14Abi3c>6H`1qg%q^Bpo4^6fQ9%%rYI78T zi~(&06E2wT=$$NV_mQrC^||wmtZ|{ktrR>sCo-l|4m9=lwrR%EnUpFifI)Lpigt@v zI$K#ELl|1zfJxvY9fkRY=vay7s0HL5K+AfgEMFL&KARm!9BG`*oqVV=!<2X)U=1?k z?E$g+J12*pM^Rv@GtC%6&-0jP;bx*C$HuWZHAxhj!A=|)mdzjOJ9Idohj1dp3!dz8 zEG)-Vj`v%;4rSZwALz>W$9N%quTht19m`E{tYas~j}4>4%>E?c?hGm?wM+@BGMcqq zmX}viIJ;+L`T+FsEMhLoDJj55IyIjfGGqT>4>H2nnsgs%CpFh+XXLeoYdFY{bhT;4 zL$~GatW%UT)Td|0iIQp(Sk=6RhkPW`S+Xo$5K4*8)8pAwjoQW3?(plx)U1YrLSG;; zJb~Zn2*}F<3)pDx(z^}HF`S{90cu#0UJp@+Xo zA3LE4e%0Fq$kmp%d`iGGi}ze@!h(41!6Uw|sIa38XyrNMNS9}f{3HKhd?srFHItu{ znryUGxupam&}S#7F??p!mUH~+q?|Im#bgO9^*0&u<>&8jjafeT9A2n2F-=Tg#{#yH zg|q$CXL`bN)cL5lkDDo{| zqh%;&nV8cSN{_p$$<$&pR|O?uK`d|U`d}TE01zKIbJZfp@%K!IIVGSdf8JTJER0(@ z{H&!1T`WQQ!YN?9U21?%$F@n05Kf*qY`s`ig~-P-7w9wbr8Lf99qYn$GiQcQso$yn zPA8t5MN=gDoZgBMtFpuia$;#w<%z~{Em-wF3;AlmxISSZW9?ExrWrW(CEQV{sT{DJSP@>_SV?)HKJed(ZS6LdB&^>#6dWrxAnz)T1-5HEYHftq5QHjR~ zrm#}+LU2lKtl9_z{cRRJVkJxMN9l4`sN;e(T1&xuZ@%QFTs(OA)U3D9Nzg^nvC!~n z?naD9TsiZcBQHl%3b^)jX#q|H3&LwC3ntuUFpw)X)>Rq_DJiQW%6qG3izEMrwW87s zYwS5I4ji@f&g0yIGTDO|rCrEwFOcH#aU4q2f8?%ti$^IILW*)VUuI-{0gcb@!;~Hdi{sDqYl5bOU*b9gWTrBdm}_G zL}$Z0ZxjrpJeqnFm&HJmw)ayr+rN zo2Rd(Zhm=(p|z=wQJbb6 z)dz90$GjovD8gJqmp1V_*vUIB&WcKYeIY^q+63tJ3>7#~WNI`f zmWSHHaxP?P$jecmnLVzF>3kpEnQe=mnAXDXPzo>P>L_FAqC>u<)dvv)KjEwbVuVlh zAIi3LA34zJLN}hN@|iF@$}A-wVKkpYQ7x%4uvCa({k`ur#DN;Js&;mGq#eK_Y`T2OnRQx@Tki}`}upFwSNR3qD8H9RlieUTrCofL+Q!*YJ#k* z%(BC^%GkR-0~c*_p(utq{RbVYGj2}{sH>~H_iz(sq-TgSj8G8<`LT9bRqw32&eq48 znq4J<^Dve9JMs)3GGD}c1&pNU>zpf3zNV_el55>Z0R=g0S0Be~G`hPwwV*SnVeS~a z^*8koSWz$K`&zrNg=o{Wl>1FhYQ1YHt<@T{(rB|Utf#L90a9X{_Qb(>s2kFbLJRq& zqS9qz+ZYLFp3{_vLfNdPu}`$NJ$9%+Uo&)=vY&n!i`P~GVjVk4U4k}zJte}J=Jyev zc!C-Zakjh0M7>d{RmNeC4wGjr9|$)<&}PIhR18x9p*d-dN#+o1UI9X-&i8-9Cu&sg zs@)b@3|QtQ>PAmmO+V{8C5SGXz(&AGqc+hKuv*T_C{~MaT9zp6?DV9k0LxhUhqj)__a-K+&_YSw?=Xz&D!PoFdt!&J@;A&Gg(Cbo)dnO&^SHDx zL1c-SJM+i0biQB^>#3kfNnlutCC))7T{`j7fWb#NHbR&Q}5GM zhjEe%R*b2R~ zquLE-k7Lt1c|6_|gS)b%`ufovV6p8cU8tF zYV2r~UY{M&SldaMa(LLPxwF~ltS`uO!kj4;qr)V9^dJ&QbSlIS57bAW8c<=Uch5Mk zh;Kf=Fq~fqRYF5kcL+x1Rw9&OUnhy3Rn*TTaCqv}^L@r1lgC2CS(77LYd3Og zeDChF4hwOP(I-j`;Xy_GFseuH`nw=HRcM-Cvh_WUhAfCZPMs@Rc{0;fWXzrlnU*qozhYnMQ4ouL*kwoKU=@|fJvRsk;ySiJatx;;GM0` zOtXgj$l`gx4D1*Y+rb;vxVh<^XIqRU1bMg#n=*tTHihwl(vNOJMb)eaoVAQD-h)&3 z!l5t{FLXTy3BWuMpuKA-KpK#FBV41z$~+otgxs_B{y|UBvisCN+tiAzl)3AG|sT-SX)?PNmjyBxXf!1(^2zo_Q zsChymObxLz(8-hIoLkSb-t+>_*C*)f^c6MGk*SWTsuHm3O(l=caar8~2Rt4u+pfIaG%pSMA4_ zLikdU?J}=z?QEQI^W2Zep((nq*@J;wR*1jJUxV$}q%%$bg&WHd zlsi?=caH4?31hG1{1*f5tlgfbrzg%%kat3yxq}7 zMx_HmYh^w09dOm{XE_b+YliBshTN1wdpwkZ4WayxtS$TghaQqL09F~YX%+@BlGJ!O zjt8+}#}dG2Mrffq=BO8aqlprC=!FP~DD;F*P@k}04gMQ7?B9BmkBak*xd3k0I%s^R z^|@J=$Xk7WkcugjV|-tMj^p6VzR#F9S=oP{%R$U8FUk762DH;poOK-R0Jk$yVZw~Z z{0VRr&Ii<&4jt~>?UDUmAiY^N0!51}3kIRDCPXh7ASBdB*Z9B^X`)JrHy z^2*qYuzFQ5A%tEa6aK{9j(E}WliRtWL^tKsAV4@hH67_G4PW4zd7&SB z6S8}=fZPhlTHoUbg`HthvRDQ}-r|fP!p(wSyk2Hb-G3qN;S`;ugPXz3Oz&A}Zj#WL zw=tn>#ia&_I0w)AIw*9C+!3%u0TiZ04usaA@%+f~!_Ib&?CMbah87jn80t#fh(Fc5 zKykn-#%+I!a5}QK7w#ORnuD&Wd7O`l?BT=N9_jp`H5-S#GQ&zZ{nan&KAPi)DjzVp!q;-a$2#;X%+VLPT3ENvH4+h9LP(+I30Za0R2x{2sDO}K zVv8MUZiy{ME4CP|mc-DpVM24^PN9|9U|O-kv|@v46<{d7XiLGOv|{6E*$%VMwuF`W z%B)d~lQizts3KKJ&8_;jo2vW}L|}@6NGk>+t=LkuVjx2Jv2nCw<7mak(PG@hM8lrF zai{n!8PH>=tp+^?Oq?=6P;63Ku}Nta023bv7skfXijAY?DFW>u##TJsP}Z?}O#dUq;{rA> zR;*iGH5ea;B1zA}8y*q94z$EckX8(KS~1vZdDufx?~((diI6|umV(J?#U`f}o19ku zunSIB3CfYVQ!p`-Q@v#yv~AxDD&27?o?=^$6Z!!}cOGI|F{o(8$e;yfl@#9+)*AR38J<^9)Q|8>qj``&Bv zz59JL=j`8Ed+mL8KW!~tE2X#nq;Xoilh68OZpZV!v?4Kx2T39nB#BUvBtk*8qOK6i z&mV!CN(HUkR&F-^U?mbHf>4kMLP$guVc6B|ON*5^4tQTEJ37?W`T;2DIMG~=i6}@6 zp&&7YGG`Pv)FURIFd@@4tla#!CU`P7UtSbXPbo|Vi>n|Ngpi6h!}Nm5YB^(j@_z?9B)L%59ALb9UdDkv#QYe9b1su@qOeUoz@ zjrB`f0tf}?j8IszvMNsEVI>&ELnw%cP!JCxX;r?U+=7}p1Ld(-swXr3LNrJNtAQX5 zgn~2>3erF*h=+A)5D%ds9zsDpgnXA9;xGR)k2-u`M;ks6ogv9BRwRLDgaXY71)33( zvqtn_DM37hf_Ml8@epEFSc48PzWuT!U2-JlfT<_Yk5Hf=p+G-E zzC`_EHofpqG+_Z1XhJB^gixRfp~JNy>&=_ujVBoalGBj|6gzXlr5XBqW(6y+{m-7` zEm0*o#Kaop5TPK42n9JrC{qVv{a`VeITq@~vN6z!P@oebUw*Rwu@-4Jd@`qKQy`?0 z3(ssr4puIhax&)>wksS8jv*A}4WS@!2nFYaNp-9azBb$zT{B9PN_#dm1c@OOB!;#h z978C3EXW9su@Oh>;iNSb%aY7{>8*=k0FM^qI>BgH@DI{#;zDkN3?dX{5TPK02z^H{ zlZ%YB(o8!#d!QYmKs!Q#c7zUh)$#(Iv}e3_<-cI@t5w_!miIKYUJ94fev+~TF(m{E zA`~QuP>>)(LB(Sn3gRIY#6u{EhmbD@X~~C&m)|pzl@%(*pGA0GiTkSlZ34gC%=A|~ z60|7vvmiGJ1-U^e$PGflSzx^w#6u{Ehfoj?p`=UdbKp!dVjvC_ArvS=C{Tn@kO5S8 z5D%ds9zsDpgb*)3f6y>Iv)64f+aZZi){5ME01CM1>J=;GAbEs>mCWHb_2nCuD()N(ne>yZ^#gG)UE!ccPV}YyhDg+%ws^+D0HCUMeX5OE>dLG<{OQ1-H#ycy=&g=IqA00n-}?9FLDt>uRgK$+H3S=A0ys$Dy+8ZzI*ssjP7W z=C#ZjfRC1D&BF(WK7cQ{2F$3b86dOEumk%y*Yw91t@9T2pV2gLfSSh5$}{o+xdoF^ z=g9zh!93#ATN?Y*nr=1%z8uH`pK^?<{cF{D5k+?@@k9_`OJFmrJ}>XqVs8Pg$cmZ7 zChYsy-f*V&@dbAAq92~V;TX&fpgjGKyeSD=EqEG=AR_mlHEY;`*sX`{ky+TvmjQW= z<3;^<7ciEQMwUSoA^+`&e4*fvzyjCY{`ixeq=pj_;9GmXco3Y2mw@NiVn;rmkk2Eq z3m~!O8xo1GLBFFpXvBVf3;x+Fe=4M}>0`h73{j{rf%=#65%Px0RIXvRY+;V98Q=O# zdRkKcaVwgN7n1LTu_MpH&>pP+`a}58xf(lC4%qN?u%`weu;V}R#1<2!WFI<2YmKW5 zDkoN4gAa=3fdtyPRo{HP)|KuLP68Lgx9>Kt1O7W#c2$SFJC@zw@!^iH?CJTxZ7qAP z_4QU)cHc$0`!C6rJ+L_U@Zy{+yKYJD!zH=0zbwgpv;@JougabG^IX}gpXYvY4T4u) zn_ImU!b@`xE|uU5x#!m8%HCd+duI)Tzj-P5vsZIv7r&aj;#CA+cqzB~Wgol@!SDFV z!7{gTODxh6Am@6x=JPdr1qHUlwwU~G;9I$JH)<~masbflhM)bu*}p5$_# z^7||PM1Bl;Ur(L;l6)rlEb`9Cw#dIuK8$=<@=jFHn|vtw-sE}m3FPOKPa!{r{Ey_v zlb=D}ZyyC?$S)>uB(Ei3M*a%9mBVA?pO9NQyh7e-UsW)adOjjQjQkq%&E#vzcSBfy zUy*;YpN6OE#+AdnwUpb${T19s-i7>ga?58Q@~-_6Rp?k0bq+}cSS`PbxqRNTEl z-isOOME)B2UgSSSSblGlpFKds>sT+Jl7D}Y^4rKalYhekxR1R3)>>bynej&Q?&Q0& z;UA{z+`i-|lUI=sCtpFHqn@$gGCoY=_)tdqiIl&Y6}UV3G%w%PRaB`08NcM$LOv?u zi^xwU@2TSMm*fk`yO2LXei3+pIvZ*IoH*_N4_of?@7Ly zyc_vQ@OI8(J7?{GyqE9oe!z0;K>6>JpBnLc@|h7|K;9gc|M}q3PZv*9#nykXAYV^@ z64PBuKK2v^JCWZ>eiHei`Mz}; z{+Vvv4DwavmhM9Gqv};~81s1(dDlkeGpXlx^7-TgDgQY6ZA~h;4abKyKz^R(TS-ovUCY`IqGGJO$l&zFpdD`FC5Wypnu( z^6&jr`Tf+>k9;HfJ!}sn$yZ#Uf_IRQBcC7(F#P(HPbL2`JJy5bjpPF_SNR*5pYyJo&Gve=zy%WeOf8KZ5-1 zTNKzlGKqXKxs}5-^4rOGrJf&=KTFPKhdYaW6S<9Ji^vbTRTZ2_J=c?8NzUQh{hE9O zc?;!#Ltb;6f`eE-Yshb60lZ52H_2zOR4|G1eEkI%1Cxzj0MNj~x}1tZ8ulb=p*Q<$-Ca8K(0^Zw}O1&BfzzL@+e>X}VG<24nO>vQ=nB){=>4L6ZrP2Op(@>|Jo zC%^j-%C{$fl>DhbD*v-?+#2#K(-&TGG^>pi^<=OWg>LKq( z-s@cjR=-u`Gs&&|Ysv2=w|ZYhzWhDaGlBYVCI6J%>irS&b3al+E(hI<;NLaxyT7Tt zjT<)a_ag7jbZy=rO5UB^=KUigJvQ&>$#{B^Zs4rN0HmSznMIq_b0O9#Pj~0WOCHbrKlxvk zkLUe$Un{rv_7=y1e~=I1IQAuZ-z_SCmTp|vt~!q0Og@(KeaS!KjiQwtC&!XszO{lK zDStBgt=lL!{il=f(N6h=l)sSt_72J~Be!+SFcxIIZrQC%R==%VZX}P_Eu*`ue7tVy zv90oW-EuIw)uXLjs(PrrjT^RZxs=?_*VZi^w^R9tSkAU?X(Jy^ZtIpDC$4zi@*;V> zZt2Mj?vtp;)-BECXOi2x@Zll)w``*YRIkLZQZi>5S6!e%LA#NrJaW^siu^fpOZRc|@{!qmzCk{b+~n7jUqx>Ezad{oZspk> z3rM*R-JS~&%jZ7iCz6|9-7s&1iaWf|9bEoo?0Bh=g!QI-xQNy>E(BHZ7d&?zX#l*6LjY~&G^$^erLD- zmzp2re**7;^E#(m$;~{liM;b23dWOvOFrXd1vikF_fS2xw1?Bm%{6>1N`{Dn|;M<~{U6jum)brG{D&5RTKObHJmXL2oKSEb6{OwSj_w>Bn;IyHmr{O03H>uDKc3}( zB;_w6U&4WnxB1)+la-Z>q*6(!o$>m~H8r=Eq>Gno18vSaab z+oJ?Oqy(Q-f}dW3FD${Y_PpF(a)@T|PrP1OQ9}NH$~UkccjI|IO@7Q81#`&PlMiG$ z*!b42M7!D{&8oPb`ulj!K(INi1V5g77INMhz&KOMpJ2ux zCa)`@XF&;mNeO-{^_OuS)t>rSli$VmVB^f=r*W}w_AT2gdj_o7uxwmVJ=CuR7e7Wm6LKR;|{k!@4EqC4bQqY%tEamTEIomvS z0{J42=X+DWk$efqNgIDICf}a&^C*7}`F+b3RFU5dF8R0h6|uXU^4G8-ujGN}$Uouw z-_rfibBmpBx9(lM+;;H1+^t!xvb%KAZEx~}pVhF9|AR~DsiOSfSy7uhYrNygr`)08 zww!KHA-{$BH~nXq(0^?SerE~(1oc0`dbIKCl@jvZb}3#yA5qT{TtJzgZ^?Tds3&Ci zi@KK3Q{j2JTgdWcF}a~7=VOUSoSegxOIrhgH6+l#8e=KWj9t6x^$ zpZXsnZ>1hvPpa0cslmtA!{Jc#4N35?U<^FRT24=uru1DA0-Uaw9A zuP9XG&hUE5-NyIzptY;TCG`J_^7lvc<|8HKU#9$O>L1NKyiI=Y3B_k&q*bCO9_4f^>;p5^>gE{ z1efckQPK4Xyk^XqJE&5eM#H-ge5TEC0Ny*?@t|-oY5r1RA|IbRG|89dFtHBLKeMZ0 zXSo*60b26Y=PeL()8Zh$uC`LF1QwC-md{}g9)|3_!=F~T_r-?~`PzAnjc`i^+h0k@ zf5)5^&3wgTA3%r`!$Jqgg-4SwCVT|A@eD*@b8WtO)(jbxihJ!uO5Dlen;Y*v2w#gP zoF>e~OgUmX; zmlN{Vu`_OC62BER)0jH4K>drQ*hC<4Y>^Q2bLD|}kdorl2omx>JCb8?Rh9+bKzDN3 ztYx>DTkq80h_|=zRr@{77o0YULvegAr0zeHJ#`_gK^QpGokC;%o~K?9d=MY4hzE(l zC6}4pHZOREZbBRb1zv|whC31-p`LitaXA$~pFn_e(6Nd%FfMepcM_mih&j7x=f_pWXSM&~i&G`W2 z#SxvlBJjR!?8vOya1zOY?-Z~fF}DF@6DekL)S;zWNErER8=G~a5Uv_ zOkO7z4@F3h1a@pgv-Y9cSfm3vIjzxVRTy?z)s>W3$dp7OklMx8u$?^*XH0-f)+GmO zNP*c50%mqOIg(}*U&f**tJO*D2ev4o6d*NstKl%;I2`5MG}xIT9OfIrINw9^W_Pog z9d|h~87)FL0s_R;0ZmkjNMHmOL72@?ETj7%jqmz>F(x@EuzsxekVEs5N>UFBNwn1J zr$@Ako*e#ed(!wg+qWj~tJj-t_7Q_GnDG^6Vk0hN`QLw!?4?6%5Gj=^$=IB+L&Xi- z(&T1f(lpM5dKrlGN6W&@h&Up^($!l=4^(Ax80aa?V{lNqrMC@>Z%w^D_Q2L@*7|m~ zQzUK6M`j5}rshng?>33$N3ne!*~*CG(ZWi}X!s)h->R;w)#&^%Ka|%Fn3zaKp&F@5 ztRk!F>@e04>u+j1nC8l8Qp;Bi4Mb(EL#vfslqtuS)B-rVQDDeDV?#FV1zEAI4S4B^ zq2XQMzi3%K8!xdFpJE#ueEp?3X%B)*6|+B9yq5f-tRx{o z{f59UN#Uo}aSxInuAf`0#-QJYIP60qFX%(z!9W+2q2;jP0OSdyBuI-T;1AITi?q_z zoT1oDVEHHGR@R0Bi}Dg;mPn9}<`FG9WvTU7$w@viMk2ui z)3E7r=<+clb2dTek#Up7RLuBPBw56U6LL^gNGm$A8k7}7zW&o>z39J9H;{@F#?2Qj zQRLQ3K3_E%MsxGxRWI-IhfjqGpYbqCl0S0d3Hc+&@#wh2C%gQS#~eFm^fCEktE%7x zD?ep4T;}DATvvD#7E-`yG3w4h$eeuLtm(}yoGjGeUDkAWa`!;JLrOP=e>lv&Eu)SZ zKSrH{4aQv`Uube_GE8X#vH~Atr?a%2eW?xPmNP1pxxOu(3A;d zF`Ly*@y;k=rURFv86|ofz%LfmA*+;gK6`FRJWktgf+V_d0w|W2BrKWcEe2dW&GnuJ zP?~Bfq==N1KI=QRmaYoaQC>Z~HKle(t?H8Io1DMKF{iuUX{R@PVlH!&vAP0rrLGkW2FG!jA-%FSg^K8RHsz|PrS`}f5TMIGVA;#$!t{p zunhz6ZKvgHW}b%2^M+Z8;^6M0H8*oDnC)%iKrV?T*}*xWW6RLu6ayCQ;@rbi6GiR$qJTpvUcd38jJ-c5hd%;)Bx9ixJ%p+ zwL|H`EzQ|BkrcL$m30dm=4K0Au9GwR)gws;>6vT$boDs%><}R50&h|1+(F}I=ZdS# zPVGC--{E2iZXz>zz%6|7N9KoBDLtuU^X=i4rRlSe$(=htp3tR`T;!xPfzPz7FVfoJ z;JHy;R{Gm|d33btLCp>ME)SQ;V~e<(ZmPxY8RjwZm?NEF96Ez{`4WusgD@+=Ex5O? zl!yCVy+}$NB>8;WY!lU{%zTBl88;Ycz`YOFp?6!Uw{$od;8_dP$@rd}rw$Q`56SGwYGT36k&LDoccRf$ zNHI@n=_nA}686Pr1+TT>8#6|gYIyF}hFIcesTK>{TdX*E)FC$o@yIB9T8;CX;-S>L zISooP7_!8I7N!w21Y6dmvyAu-)zb*Ci3_H^bkwsd*TM@mHWnXmyuoE(?n^QMM%vyS9*ab1IBPTN=J-%lIwQCHT?Ys$B}d z-rr8kltVs`)uAogW!Y#M$SfLtx`p{Svub&HfRU(;r!STPrXO!!H<61cTIZi1sIO)8 zqM8NMlRH+KIMSjyge2V3%rstHvT8T!F8Q)(XvRBTn zAO9+SrZfXyjXdKN|6-N3b=C7`K*{tbJc6`fLsPwI(L>eMbL!?>2=*51Y##+`yMi&` zCgC|vGsKU4OZY;>|8pO&;kfqr!|N3OchT0_Enn~AQTe7J`>RAShd+ydC*#`|ueW4= zA{+jOA@I*FTV`AQ=NaF&11W6jb_gS5e-!@YRVjup?z-`bOaU9{Jh!ROUY}KRf^VkdJrOQ}+v%mQ;(>FPY_@9MsyAf1!YVqy6w>HN2 z2Q{kyUlHuf?zi}pzLUh~cV2Cm>G^NajUT^6-qN*lX^-P!`m6cf*J>DCmEXSpfBMhj z+qOK4Z{LlrXZ#MDxGAIk0#Z?hP?vcK-JMZig#1{xTEf zwjvHgOe65u%{`KS(~FlK=n! 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; } From ab31776658cda99d6d8a788f6e1a07b23bda325e Mon Sep 17 00:00:00 2001 From: amir Date: Thu, 23 Apr 2026 14:13:48 +0100 Subject: [PATCH 5/7] Reworking IO Ring pipeline to fully support multiple infilght files Reworking the filequeue, the buffer chaining logic and the error handling. Renaming functions. Fix bug in arena. --- .gitignore | 1 + arena.c | 6 +- base.h | 1 + binaries/changelog.txt | 2 +- file_hasher | Bin 245536 -> 0 bytes file_hasher.c | 22 +- platform.c | 716 ++++++++++++++++++++--------------------- 7 files changed, 365 insertions(+), 383 deletions(-) delete mode 100644 file_hasher diff --git a/.gitignore b/.gitignore index 19b273a..91ce0c9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ temp_code.c /.cache/clangd/index /file_hasher /io_uring_test +/file_hasher diff --git a/arena.c b/arena.c index 8c03d99..61767d5 100644 --- a/arena.c +++ b/arena.c @@ -437,12 +437,14 @@ void *arena_push(mem_arena **arena_ptr, u64 size, bool zero) { // mk push Commit memory if needed ------------------------------------------------------------ */ - if (local_post > selected->commit_pos) { - u64 new_commit = ALIGN_UP_POW2(local_post, arena_pagesize()); + if (local_post > selected->commit_pos - + ALIGN_UP_POW2(sizeof(mem_arena), selected->align)) { + u64 new_commit = ALIGN_UP_POW2(local_post + ALIGN_UP_POW2(sizeof(mem_arena), selected->align), arena_pagesize()); new_commit = MIN(new_commit, selected->reserve_size); if (!plat_mem_commit((u8 *)selected + selected->commit_pos, new_commit - selected->commit_pos)) { + printf("ERROR: Could not commit memory!\n"); return NULL; } diff --git a/base.h b/base.h index 4439968..24b9e18 100644 --- a/base.h +++ b/base.h @@ -46,6 +46,7 @@ #include #include #include +#include /* ------------------------------------------------------------ Base types diff --git a/binaries/changelog.txt b/binaries/changelog.txt index e2b14d8..9062932 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 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. +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 cache 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/file_hasher b/file_hasher deleted file mode 100644 index 8c12d074d16ec49d546cfa63db36d9f337926f66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245536 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 diff --git a/file_hasher.c b/file_hasher.c index 7948f12..52e2c9b 100644 --- a/file_hasher.c +++ b/file_hasher.c @@ -77,14 +77,19 @@ int main(int argc, char **argv) { // Detect hardware // ------------------------------- // --- Windows: detect PHYSICAL cores (not logical threads) --- - size_t hw_threads = platform_physical_cores(); + uint32_t cpu_cores = platform_physical_cores(); // Logical threads = CPU cores * 2 - size_t num_threads = hw_threads * 2; + uint32_t cpu_threads = cpu_cores * 2; - printf("Starting thread pool: %zu threads (CPU cores: %zu)\n", num_threads, - hw_threads); - printf(" Selected instruction set: %s\n", get_xxhash_instruction_set()); + uint32_t num_scan_threads = cpu_threads; + uint32_t num_hash_threads = cpu_threads; + // uint32_t num_hash_threads = 1; + + printf("%d cores %d threads CPU detected with %s instruction set\n" + "Starting thread pool: %d scanning and %d hashing threads\n", + cpu_cores, cpu_threads, get_xxhash_instruction_set(), num_scan_threads, + num_hash_threads); // Align IO Ring block size to the system page size g_ioring_buffer_size = ALIGN_UP_POW2(g_ioring_buffer_size, g_pagesize); @@ -119,9 +124,6 @@ int main(int argc, char **argv) { // } // Starting hash threads - size_t num_hash_threads = num_threads; - // size_t num_hash_threads = 1; - WorkerContext workers[num_hash_threads]; Thread *hash_threads = arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true); @@ -130,7 +132,7 @@ int main(int argc, char **argv) { workers[i].arena = arena_create(¶ms); workers[i].file_queue = &file_queue; - if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker_io_ring, + if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker_ioring, &workers[i]) != 0) { fprintf(stderr, "Failed to create hash thread %zu\n", i); exit(1); @@ -178,8 +180,6 @@ int main(int argc, char **argv) { } // Starting scan threads - size_t num_scan_threads = num_threads; - ScannerContext scanners[num_scan_threads]; Thread *scan_threads = arena_push(&gp_arena, sizeof(Thread) * num_scan_threads, true); diff --git a/platform.c b/platform.c index b277ea6..deb0e63 100644 --- a/platform.c +++ b/platform.c @@ -887,11 +887,11 @@ static THREAD_RETURN progress_thread(void *arg) { // ======================== Hash worker IO Ring ======================== // -------------------------- Configuration --------------------------- -// #define IORING_BUFFER_SIZE (KiB(32)) #define IORING_BUFFER_SIZE (KiB(256)) #define NUM_BUFFERS_PER_THREAD 32 -#define MAX_ACTIVE_FILES 1 +#define MAX_ACTIVE_FILES 32 #define SUBMIT_TIMEOUT_MS 30000 +// #define IORING_DEBUG // Uncomment to print some errors // Globals u64 g_ioring_buffer_size = 4096 * 64; @@ -901,108 +901,116 @@ static atomic_uint_fast64_t g_io_ring_fallbacks = 0; #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 +typedef HIORING IoRingHandle; +#define BUILD_READ_RETURN_VALUE HRESULT + +typedef struct { + HRESULT ResultCode; + uint32_t Information; + uintptr_t UserData; +} IoRingCQE; #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; +} IoUring; -typedef AsyncIoRingImpl *AsyncIoRing; -typedef int AsyncIoHandle; +typedef IoUring *IoRingHandle; typedef struct iovec IORING_BUFFER_INFO; -#define INVALID_ASYNC_IO_HANDLE (-1) -#define SUBMIT_READ_RETURN_VALUE int +#define BUILD_READ_RETURN_VALUE int typedef struct { int ResultCode; uint32_t Information; uintptr_t UserData; -} AsyncIoCompletion; +} IoRingCQE; #endif typedef struct IoBuffer IoBuffer; typedef struct FileReadContext { - FileHandle hFile; - uint64_t file_size; - 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) - }; FileEntry *fe; - - // Completion tracking - int reads_submitted; - int reads_completed; - - int active_reads; - int failed_reads; - - int reads_hashed; - uint64_t bytes_hashed; + uint64_t file_size; // For in-order hashing - uint64_t next_hash_offset; uint64_t next_read_offset; IoBuffer *head; IoBuffer *tail; + // Completion tracking + uint64_t bytes_hashed; + uint32_t reads_hashed; + + uint32_t reads_submitted; + uint32_t reads_completed; + + uint32_t active_reads; + + union { + XXH3_state_t hash_state; // For incremental hash (large files) + XXH128_hash_t single_hash; // For single-shot hash (small files) + }; + + FileHandle file_handle; + + bool use_incremental_hash; + + bool completed; + } FileReadContext; // -------------------------- Buffer structure --------------------------- -typedef struct IoBuffer { - void *data; - uint64_t offset; - size_t size; - size_t bytes_read; - SUBMIT_READ_RETURN_VALUE result; +#define IO_PENDING INT_MIN - int buffer_id; +typedef struct IoBuffer { + FileReadContext *file; + void *data; + size_t size; + uint64_t offset; + size_t bytes_read; + + BUILD_READ_RETURN_VALUE result; int completed; - FileReadContext *file; - struct IoBuffer *next; + int buffer_id; } IoBuffer; // Thread-local I/O Ring context typedef struct ThreadIoContext { - AsyncIoRing ring; + IoRingHandle 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; + bool submitting; + +#if defined(__linux__) + bool use_registered_buffers; +#endif - int use_registered_buffers; } ThreadIoContext; typedef struct { uint32_t MaxSubmissionQueueSize; uint32_t MaxCompletionQueueSize; uint32_t MaxVersion; -} AsyncIoCapabilities; +} IoRingCapabilities; // ----------------------------- Async I/O Abstraction ------------------------- #if defined(_WIN32) || defined(_WIN64) // Windows I/O Ring functions -static void async_io_query_capabilities(AsyncIoCapabilities *caps) { +static void ioring_query_capabilities(IoRingCapabilities *caps) { IORING_CAPABILITIES win_caps; QueryIoRingCapabilities(&win_caps); caps->MaxSubmissionQueueSize = win_caps.MaxSubmissionQueueSize; @@ -1010,32 +1018,30 @@ static void async_io_query_capabilities(AsyncIoCapabilities *caps) { caps->MaxVersion = win_caps.MaxVersion; } -static void *async_io_create_completion_event(void) { +static void *ioring_create_completion_event(void) { return CreateEvent(NULL, FALSE, FALSE, NULL); } -static void async_io_set_completion_event(AsyncIoRing ring, void *event) { +static void ioring_set_completion_event(IoRingHandle ring, void *event) { SetIoRingCompletionEvent(ring, event); } -static void async_io_wait_for_completion(ThreadIoContext *ctx) { +static void ioring_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) { +static int create_ioring(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(); + thread_ctx->completion_event = ioring_create_completion_event(); if (thread_ctx->completion_event) { - async_io_set_completion_event(thread_ctx->ring, - thread_ctx->completion_event); + ioring_set_completion_event(thread_ctx->ring, thread_ctx->completion_event); } return SUCCEEDED(hr) ? 0 : -1; } @@ -1045,78 +1051,83 @@ static int async_io_create_ring(ThreadIoContext *thread_ctx, #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) { +static int ioring_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); + ioring_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) { +static int ioring_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; + char error_msg[256]; 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); + NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), error_msg, + sizeof(error_msg), NULL); if (size > 0) { - fprintf(stderr, "Error registering buffers: %s (0x%08X)\n", messageBuffer, + fprintf(stderr, "Error registering buffers: %s (0x%08X)\n", error_msg, (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); + ioring_submit(thread_ctx, 0, 0, NULL); return hr; } -static void async_io_close_event(void *event) { CloseHandle(event); } +static void ioring_close_event(void *event) { CloseHandle(event); } -static int async_io_close_ring(ThreadIoContext *thread_ctx) { +static int close_ioring(ThreadIoContext *thread_ctx) { if (thread_ctx->completion_event) - async_io_close_event(thread_ctx->completion_event); + ioring_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); +static BUILD_READ_RETURN_VALUE ioring_build_read(ThreadIoContext *thread_ctx, + FileReadContext *file_ctx, + uint32_t buffer_id, + size_t size, uint64_t offset, + uintptr_t user_data) { + IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(file_ctx->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); + if (FAILED(hr)) { + char error_msg[256]; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + error_msg, sizeof(error_msg), NULL); + + fprintf(stderr, + "ERROR: Building read error for file: %s - Code: %s (0x%08X)\n", + file_ctx->fe->path, error_msg, (unsigned int)hr); + } return hr; } -typedef struct { - HRESULT ResultCode; - uint32_t Information; - uintptr_t UserData; -} AsyncIoCompletion; - -static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { +static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { IORING_CQE win_cqe; while (1) { @@ -1128,10 +1139,10 @@ static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { 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) + // 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; @@ -1145,9 +1156,18 @@ static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win_cqe.ResultCode, 0, error_msg, sizeof(error_msg), NULL); + + // Try to get the file path from the buffer + IoBuffer *buf = (IoBuffer *)win_cqe.UserData; + const char *file_path = "unknown"; + if (buf && buf->file && buf->file->fe) { + file_path = buf->file->fe->path; + } + fprintf(stderr, - "WARNING: I/O completion error - Code: 0x%lx, Error: %s\n", - win_cqe.ResultCode, error_msg); + "WARNING: I/O completion error for file '%s' - Code: 0x%lx, " + "Error: %s\n", + file_path, win_cqe.ResultCode, error_msg); } return 1; @@ -1156,7 +1176,7 @@ static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { #elif defined(__linux__) // Linux io_uring functions implementation -static void async_io_query_capabilities(AsyncIoCapabilities *caps) { +static void ioring_query_capabilities(IoRingCapabilities *caps) { // Get system limits for io_uring long max_entries = sysconf(_SC_IOV_MAX); if (max_entries <= 0) @@ -1169,9 +1189,8 @@ static void async_io_query_capabilities(AsyncIoCapabilities *caps) { } // 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)); +static int create_ioring(ThreadIoContext *thread_ctx, uint32_t queue_size) { + IoUring *impl = (IoUring *)calloc(1, sizeof(IoUring)); if (!impl) return -1; @@ -1210,25 +1229,76 @@ static int async_io_create_ring(ThreadIoContext *thread_ctx, #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; +static int ioring_register_buffers(ThreadIoContext *thread_ctx, + uint32_t num_buffers, + IORING_BUFFER_INFO *buf_info) { + IoUring *impl = (IoUring *)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"); + if (hr == -ENOMEM) { + struct rlimit limit; + getrlimit(RLIMIT_MEMLOCK, &limit); + + fprintf(stderr, + "WARNING: Buffer registration failed due to memlock limits " + "(ENOMEM).\n" + "Increase the limit to solve this warning.\n"); + + // The memlock limit in Linux restricts the amount of memory a process can + // "lock" into physical RAM using the mlock() family of system calls. This + // prevents the operating system from swapping that memory out to disk. + // And registering buffers will lock the buffers memory so the hardware + // can access it directly without kernel intervention. Increase the limit + // to be able to register the buffers. + // + // **Modifying the Limit: + // The method for changing the memlock limit depends on whether you are + // managing a user session or a system service. + // 1. For Users and Interactive Sessions + // To permanently increase the limit for a specific user or group, modify + // the /etc/security/limits.conf file. Add the following lines: + // # Example for a specific user (replace 'username') + // username soft memlock unlimited + // username hard memlock unlimited + // + // # Example for all users + // * soft memlock unlimited + // * hard memlock unlimited + // + // Soft Limit: The value the user starts with; can be raised up to the + // hard limit. + // + // Hard Limit: The absolute maximum; only a privileged user + // (root) can increase this. Values: Can be set in Kilobytes (KB) or as + // unlimited. + // + // 2. For Systemd Services + // Settings in limits.conf do not affect background services managed by + // systemd. To increase the limit for a service, edit its service file + // (e.g., /etc/systemd/system/myservice.service) and add: + // + // [Service] + // LimitMEMLOCK=infinity + + } else { + // For any other error (e.g., EFAULT, EBUSY, EINVAL) + fprintf(stderr, "Error registering buffers: %s (code: %d)\n", + strerror(-hr), hr); + } + + fprintf(stderr, "Falling back to unregistered buffers (performance may " + "be reduced).\n"); } + + thread_ctx->use_registered_buffers = (hr == 0); 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; +static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, + uint32_t timeout_ms, uint32_t *submitted) { + IoUring *impl = (IoUring *)thread_ctx->ring; if (!impl) return -1; @@ -1251,27 +1321,26 @@ static int async_io_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, return 0; } -static int async_io_close_ring(ThreadIoContext *thread_ctx) { - AsyncIoRingImpl *impl = (AsyncIoRingImpl *)thread_ctx->ring; +static int close_ioring(ThreadIoContext *thread_ctx) { + IoUring *impl = (IoUring *)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; +static int ioring_build_read(ThreadIoContext *thread_ctx, + FileReadContext *file_ctx, uint32_t buffer_id, + size_t size, uint64_t offset, + uintptr_t user_data) { + IoRingHandle ring = thread_ctx->ring; + IoUring *impl = (IoUring *)ring; if (!impl) return -1; @@ -1281,22 +1350,21 @@ static int async_io_build_read(ThreadIoContext *thread_ctx, return -1; } - ThreadIoContext *ctx = thread_ctx; // or pass it properly TODO : look here - - void *buf = ctx->buffers[buffer_id].data; + void *buf = thread_ctx->buffers[buffer_id].data; if (thread_ctx->use_registered_buffers) { - io_uring_prep_read_fixed(sqe, file_handle, buf, size, offset, buffer_id); + io_uring_prep_read_fixed(sqe, file_ctx->file_handle, buf, size, offset, + buffer_id); } else { - io_uring_prep_read(sqe, file_handle, buf, size, offset); + io_uring_prep_read(sqe, file_ctx->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; +static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { + IoUring *impl = (IoUring *)ring; struct io_uring_cqe *cqe_ptr = NULL; @@ -1333,8 +1401,17 @@ static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { // Check for error and print warning if (res < 0) { - fprintf(stderr, "WARNING: I/O completion error - Code: %d, Error: %s\n", - res, strerror(-res)); + // Try to get the file path from the buffer + IoBuffer *buf = (IoBuffer *)cqe->UserData; + const char *file_path = "unknown"; + if (buf && buf->file && buf->file->fe) { + file_path = buf->file->fe->path; + } + + fprintf( + stderr, + "WARNING: I/O completion error for file '%s' - Code: %d, Error: %s\n", + file_path, res, strerror(-res)); } return 1; @@ -1343,8 +1420,8 @@ static int async_io_pop_completion(AsyncIoRing ring, AsyncIoCompletion *cqe) { #endif // OS-agnostic helper macros -#define ASYNC_IO_SUCCEEDED(result) ((result) >= 0) -#define ASYNC_IO_FAILED(result) ((result) < 0) +#define IORING_SUCCEEDED(result) ((result) >= 0) +#define IORING_FAILED(result) ((result) < 0) // ---------------------- FIFO queue operations --------------------------- typedef struct FileQueue { @@ -1364,55 +1441,51 @@ static FileReadContext *fq_push(FileQueue *fq) { return f; } -static FileReadContext *fq_peek(FileQueue *fq) { +static FileReadContext *fq_peek_tail(FileQueue *fq) { if (fq->count == 0) return NULL; - return &fq->files[fq->head]; + + int idx = (fq->tail - 1 + MAX_ACTIVE_FILES) % MAX_ACTIVE_FILES; + return &fq->files[idx]; // return the newest file } -static void fq_pop(FileQueue *fq) { - fq->head = (fq->head + 1) % MAX_ACTIVE_FILES; - fq->count--; +static FileReadContext *fq_peek_at(FileQueue *fq, int index) { + if (index < 0 || index >= fq->count) + return NULL; + + int idx = (fq->head + index) % MAX_ACTIVE_FILES; + return &fq->files[idx]; } -static void fq_remove_at(FileQueue *fq, int index) { - if (fq->count == 0) - return; +static void fq_trim(FileQueue *fq) { + while (fq->count > 0) { + FileReadContext *f = &fq->files[fq->head]; - int remove_idx = (fq->head + index) % MAX_ACTIVE_FILES; + if (!f->completed) + break; - 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]; + fq->head = (fq->head + 1) % MAX_ACTIVE_FILES; + fq->count--; } - - // 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) { +// ----------------- Initialize thread context ----------------------- +static ThreadIoContext *ioring_init_thread(void) { ThreadIoContext *thread_ctx = (ThreadIoContext *)calloc(1, sizeof(ThreadIoContext)); if (!thread_ctx) return NULL; // Query I/O Ring capabilities - AsyncIoCapabilities caps; - async_io_query_capabilities(&caps); + IoRingCapabilities caps; + ioring_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 - if (async_io_create_ring(thread_ctx, queue_size) != 0) { + if (create_ioring(thread_ctx, queue_size) != 0) { free(thread_ctx); thread_ctx = NULL; return NULL; @@ -1430,14 +1503,14 @@ static ThreadIoContext *io_ring_init_thread(void) { if (base_ptr) { if (!plat_mem_commit(base_ptr, buf_pool_size)) { plat_mem_release(base_ptr, 0); - async_io_close_ring(thread_ctx); + close_ioring(thread_ctx); free(thread_ctx); thread_ctx = NULL; return NULL; } } else { - async_io_close_ring(thread_ctx); + close_ioring(thread_ctx); free(thread_ctx); thread_ctx = NULL; return NULL; @@ -1456,22 +1529,21 @@ static ThreadIoContext *io_ring_init_thread(void) { // Register buffers int hr = - async_io_register_buffers(thread_ctx, NUM_BUFFERS_PER_THREAD, buf_info); + ioring_register_buffers(thread_ctx, NUM_BUFFERS_PER_THREAD, buf_info); - thread_ctx->use_registered_buffers = (hr == 0); - thread_ctx->submitting = 1; + thread_ctx->submitting = true; thread_ctx->num_submissions = 0; thread_ctx->active_files = 0; return thread_ctx; } -static void io_ring_cleanup_thread(ThreadIoContext *thread_ctx) { +static void ioring_cleanup_thread(ThreadIoContext *thread_ctx) { if (!thread_ctx) return; if (thread_ctx->ring) - async_io_close_ring(thread_ctx); + close_ioring(thread_ctx); // Free the buffer pool memory if (thread_ctx->buffers[0].data) { @@ -1494,7 +1566,8 @@ static IoBuffer *get_free_buffer(ThreadIoContext *ctx) { IoBuffer *buf = &ctx->buffers[idx]; buf->completed = 0; buf->bytes_read = 0; - buf->result = 0; + buf->result = IO_PENDING; + buf->next = NULL; return buf; } @@ -1513,104 +1586,63 @@ static int build_read(ThreadIoContext *thread_ctx, FileReadContext *file_ctx, buf->size = size; buf->file = file_ctx; - SUBMIT_READ_RETURN_VALUE result = - async_io_build_read(thread_ctx, file_ctx->hFile, buf->buffer_id, size, - offset, (uintptr_t)buf); + BUILD_READ_RETURN_VALUE result = ioring_build_read( + thread_ctx, file_ctx, buf->buffer_id, size, offset, (uintptr_t)buf); - if (ASYNC_IO_SUCCEEDED(result)) { + if (IORING_SUCCEEDED(result)) { file_ctx->active_reads++; file_ctx->reads_submitted++; thread_ctx->num_submissions++; } else { - buf->completed = 1; + buf->completed = true; buf->result = result; // Store the error code return_buffer(thread_ctx, buf); } return result; } -// ------------ Link completed buffers in an ordered list ------------- -static void insert_buffer_ordered(FileReadContext *file, IoBuffer *buf) { - buf->next = NULL; - - // empty list - if (!file->head) { - file->head = file->tail = buf; - return; - } - - // insert at head - if (buf->offset < file->head->offset) { - buf->next = file->head; - file->head = buf; - return; - } - - // find position - IoBuffer *cur = file->head; - - while (cur->next && cur->next->offset < buf->offset) { - cur = cur->next; - } - - buf->next = cur->next; - cur->next = buf; - - if (!buf->next) - file->tail = buf; -} - // -------------------------- Process completions --------------------------- static void process_completions(ThreadIoContext *thread_ctx, FileQueue *fq) { - AsyncIoCompletion cqe; + IoRingCQE cqe; - // Keep processing as long as there are completions available - while (async_io_pop_completion(thread_ctx->ring, &cqe) == 1) { + while (ioring_pop_completion(thread_ctx->ring, &cqe) == 1) { IoBuffer *buf = (IoBuffer *)cqe.UserData; FileReadContext *file = buf->file; buf->result = cqe.ResultCode; buf->bytes_read = cqe.Information; - buf->completed = 1; file->active_reads--; file->reads_completed++; thread_ctx->num_submissions--; - - if (ASYNC_IO_SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) { - - buf->next = NULL; - - insert_buffer_ordered(file, buf); - - } else { - file->failed_reads++; - return_buffer(thread_ctx, buf); - } } } // -------------------- File operations ----------------------- -static int init_file(FileReadContext *f, FileEntry *fe) { - memset(f, 0, sizeof(*f)); +static int init_file(FileReadContext *file, FileEntry *fe) { + memset(file, 0, sizeof(*file)); - f->fe = fe; - f->file_size = fe->size_bytes; + file->fe = fe; + file->file_size = fe->size_bytes; + file->head = file->tail = 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); + file->file_handle = os_file_open(fe->path, FLAG_ASYNC_DIRECT_READ); - if (f->hFile == INVALID_ASYNC_IO_HANDLE) { + if (file->file_handle == INVALID_FILE_HANDLE) { + +#ifdef IORING_DEBUG + printf("ERROR: Could not open file %s\n", fe->path); +#endif return 0; } // Determine hash method based on file size - if (f->file_size > g_ioring_buffer_size) { - f->use_incremental_hash = true; - XXH3_128bits_reset(&f->hash_state); + if (file->file_size > g_ioring_buffer_size) { + file->use_incremental_hash = true; + XXH3_128bits_reset(&file->hash_state); } else { - f->use_incremental_hash = false; + file->use_incremental_hash = false; } return 1; } @@ -1620,9 +1652,11 @@ static void finalize_file(FileReadContext *file, ThreadIoContext *thread_ctx, FileEntry *fe = file->fe; + os_file_close(file->file_handle); + char hash[HASH_STRLEN]; - if (file->failed_reads == 0 && file->bytes_hashed == file->file_size) { + if (file->bytes_hashed == file->file_size) { if (file->use_incremental_hash) { // Large file: digest the accumulated hash state XXH128_hash_t h = XXH3_128bits_digest(&file->hash_state); @@ -1635,10 +1669,12 @@ static void finalize_file(FileReadContext *file, ThreadIoContext *thread_ctx, (unsigned long long)file->single_hash.low64); } } else { +#ifdef IORING_DEBUG + printf("WARNING: Fallback for path: %s\n", fe->path); +#endif + atomic_fetch_add(&g_io_ring_fallbacks, 1); 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]; @@ -1657,219 +1693,164 @@ static void finalize_file(FileReadContext *file, ThreadIoContext *thread_ctx, atomic_fetch_add(&g_files_hashed, 1); } -// -------------------- Hash head file ----------------------- -static void hash_head_file(ThreadIoContext *thread_ctx, FileQueue *fq, - WorkerContext *worker_ctx) { - - FileReadContext *file = fq_peek(fq); - if (!file) - return; - - // 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; - - // Consume from head - file->head = buf->next; - if (!file->head) - file->tail = NULL; - - // 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); - } - - 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++; - } - - // Move to next offset - file->next_hash_offset += buf->size; - - // Return buffer to pool - return_buffer(thread_ctx, buf); - } - - // Finalize file when all reads are complete - if (file->active_reads == 0 && file->bytes_hashed >= file->file_size) { - finalize_file(file, thread_ctx, worker_ctx); - os_file_close(file->hFile); - fq_pop(fq); - thread_ctx->active_files--; - } -} - +// -------------------- Hash 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]; + for (int i = 0; i < fq->count; i++) { - bool progressed = false; + FileReadContext *file = fq_peek_at(fq, i); + if (!file || file->completed) + continue; - // ---- HASH READY BUFFERS ---- + // ---- HASH READY BUFFERS IN ORDER ---- while (file->head) { IoBuffer *buf = file->head; - if (buf->offset != file->bytes_hashed) + // CQE not received yet + if (buf->result == IO_PENDING) break; - progressed = true; - + // Consume buffer file->head = buf->next; - if (!file->head) - file->tail = NULL; - size_t bytes_to_hash = buf->bytes_read; + if (IORING_SUCCEEDED(buf->result) && buf->bytes_read > 0) { - if (buf->offset + buf->bytes_read > file->file_size) { - bytes_to_hash = file->file_size - buf->offset; - } + size_t bytes_to_hash = buf->bytes_read; - 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); + 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) { + 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); + } + + file->reads_hashed++; + + } else if (buf->bytes_read == 0 && IORING_SUCCEEDED(buf->result)) { + file->reads_hashed++; // EOF + } else { + finalize_file(file, thread_ctx, worker_ctx); + file->completed = true; } return_buffer(thread_ctx, buf); } // ---- FINALIZE ---- - if (file->active_reads == 0 && file->bytes_hashed >= file->file_size) { + if (!file->completed && 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); + file->completed = true; thread_ctx->active_files--; - - continue; } - i++; } + + // Clean up completed files from the head + fq_trim(fq); } -// ------------- Submit pending reads - fill all free buffers ----------------- +// ------------------ Build pending reads ---------------------- static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, WorkerContext *worker_ctx) { MPMCQueue *file_queue = worker_ctx->file_queue; - // Try to submit reads for the current head file - FileReadContext *f = fq_peek(fq); + FileReadContext *file = fq_peek_tail(fq); for (;;) { - if (f) { - while (f->next_read_offset < f->file_size) { + // ---------------- BUILD READS FOR CURRENT FILE ---------------- + if (file) { + while (file->next_read_offset < file->file_size) { IoBuffer *buf = get_free_buffer(thread_ctx); if (!buf) return; - size_t remaining = f->file_size - f->next_read_offset; - size_t size; - - // Check if this is the last read and the file size is not - // sector-aligned - int is_last_read = (remaining <= g_ioring_buffer_size); + size_t remaining = file->file_size - file->next_read_offset; + size_t bytes_to_read; if (remaining >= g_ioring_buffer_size) { - // Normal full read - size = g_ioring_buffer_size; + bytes_to_read = g_ioring_buffer_size; } else { - // Last read - handle partial sector - if (remaining % g_pagesize != 0) { - size = ALIGN_UP_POW2(remaining, g_pagesize); - - } else { - size = remaining; - } + bytes_to_read = ALIGN_UP_POW2(remaining, g_pagesize); } - SUBMIT_READ_RETURN_VALUE hr = - build_read(thread_ctx, f, buf, f->next_read_offset, size); + // Initialize buffer + buf->file = file; + buf->offset = file->next_read_offset; + buf->size = bytes_to_read; - if (ASYNC_IO_FAILED(hr)) { + // Chain buffer + if (!file->head) { + file->head = buf; + } else { + file->tail->next = buf; + } + file->tail = buf; + + BUILD_READ_RETURN_VALUE hr = + ioring_build_read(thread_ctx, file, buf->buffer_id, bytes_to_read, + buf->offset, (uintptr_t)buf); + + if (IORING_FAILED(hr)) { + // mark failure and stop this file return_buffer(thread_ctx, buf); - f->failed_reads++; - f->active_reads = 0; - f->reads_submitted = 0; - f->next_read_offset = f->file_size; + finalize_file(file, thread_ctx, worker_ctx); + file->completed = true; break; } - f->next_read_offset += size; + file->active_reads++; + file->reads_submitted++; + thread_ctx->num_submissions++; + + file->next_read_offset += bytes_to_read; } } - // Add new file if possible + // ---------------- ADD NEW FILE ---------------- if (!thread_ctx->submitting) return; - if (thread_ctx->active_files >= MAX_ACTIVE_FILES) + if (fq->count >= MAX_ACTIVE_FILES) return; FileEntry *fe = mpmc_pop(file_queue); if (!fe) { - thread_ctx->submitting = 0; + thread_ctx->submitting = false; return; } - FileReadContext *newf = fq_push(fq); + FileReadContext *newfile = fq_push(fq); - if (!init_file(newf, fe)) { - // File can't be opened with NO_BUFFERING, process with fallback - char hash[HASH_STRLEN]; - finalize_file(newf, thread_ctx, worker_ctx); - fq_pop(fq); + if (!init_file(newfile, fe)) { + finalize_file(newfile, thread_ctx, worker_ctx); + newfile->completed = true; continue; } - f = newf; + file = newfile; thread_ctx->active_files++; } } - // -------------------------- Hash worker I/O Ring --------------------------- -static THREAD_RETURN hash_worker_io_ring(void *arg) { - WorkerContext *ctx = (WorkerContext *)arg; +static THREAD_RETURN hash_worker_ioring(void *arg) { + WorkerContext *worker_ctx = (WorkerContext *)arg; // Init IO ring - ThreadIoContext *thread_ctx = io_ring_init_thread(); + ThreadIoContext *thread_ctx = ioring_init_thread(); if (!thread_ctx || !thread_ctx->ring) { printf("I/O Ring unavailable, using buffered I/O\n"); return hash_worker(arg); @@ -1886,28 +1867,25 @@ static THREAD_RETURN hash_worker_io_ring(void *arg) { for (;;) { // Submit new reads - build_pending_reads(thread_ctx, &fq, ctx); + build_pending_reads(thread_ctx, &fq, worker_ctx); - wait_count = MIN(thread_ctx->num_submissions, NUM_BUFFERS_PER_THREAD - 2); + wait_count = MIN(thread_ctx->num_submissions, NUM_BUFFERS_PER_THREAD - 6); submitted = 0; - // async_io_submit(ring_ctx->ring, 0, 0, &submitted); - async_io_submit(thread_ctx, wait_count, 0, &submitted); + ioring_submit(thread_ctx, wait_count, 0, &submitted); // 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); + // printf( + // "Free buffers: %d, Submissions: %d, Active files: %d, fq count: + // %d\n", thread_ctx->free_count, thread_ctx->num_submissions, + // thread_ctx->active_files, fq.count); // debug end // Hash files - for (int i = 0; i < fq.count; i++) { - hash_head_file(thread_ctx, &fq, ctx); - } - // hash_ready_files(ring_ctx, &fq, ctx); + hash_ready_files(thread_ctx, &fq, worker_ctx); // Exit condition if (!thread_ctx->submitting && thread_ctx->active_files == 0 && @@ -1916,6 +1894,6 @@ static THREAD_RETURN hash_worker_io_ring(void *arg) { } } - io_ring_cleanup_thread(thread_ctx); + ioring_cleanup_thread(thread_ctx); return THREAD_RETURN_VALUE; } From 3393129c5f69a2fcf2453f015b99336dffde860c Mon Sep 17 00:00:00 2001 From: amir Date: Fri, 24 Apr 2026 15:30:04 +0100 Subject: [PATCH 6/7] Implementing registered files in io_uring The windows implementation is disabled, currently registering files in IO Ring when there is inflight IO operations causes corruptions. Implementing a config file. Some code cleanup --- base.h | 9 +- binaries/changelog.txt | 3 +- config.h | 27 +++++ file_hasher.c | 77 +++++---------- platform.c | 220 +++++++++++++++++++++++++---------------- 5 files changed, 194 insertions(+), 142 deletions(-) create mode 100644 config.h diff --git a/base.h b/base.h index 24b9e18..3d8e91a 100644 --- a/base.h +++ b/base.h @@ -1,4 +1,5 @@ #pragma once + #define _CRT_SECURE_NO_WARNINGS #if defined(_WIN32) || defined(_WIN64) @@ -10,7 +11,7 @@ #include #include #include -#include +#include // Needs to be included before stdatomic to avoid errors #include #include #include @@ -26,18 +27,19 @@ #include #include #include +#include #include #include +#include #include #include #include -#include -#include #endif #include #include #include +#include #include #include #include @@ -46,7 +48,6 @@ #include #include #include -#include /* ------------------------------------------------------------ Base types diff --git a/binaries/changelog.txt b/binaries/changelog.txt index 9062932..e619f7e 100644 --- a/binaries/changelog.txt +++ b/binaries/changelog.txt @@ -50,8 +50,9 @@ Fixing user prompt parsing Reorganising the code Improving the scan function -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 cache completely, registred buffers, 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 cache completely, registered buffers (and registered files in io_uring), 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 +Implementing a config file diff --git a/config.h b/config.h new file mode 100644 index 0000000..3aa7208 --- /dev/null +++ b/config.h @@ -0,0 +1,27 @@ + +#define FILE_HASHES_TXT "file_hashes.txt" +#define HASH_STRLEN 33 // 128-bit hex (32 chars) + null +#define MAX_PATHLEN 4096 +#define READ_BLOCK (KiB(64)) + +#define MULTI_THREADED true + +// -------------------- IO Ring Configuration ---------------------- +#define USE_IORING 1 + +#if USE_IORING +#define IORING_BUFFER_SIZE (KiB(256)) +#define NUM_BUFFERS_PER_THREAD 32 +#define MAX_ACTIVE_FILES 32 +#define SUBMIT_TIMEOUT_MS 30000 + +#define IORING_DEBUG_PRINTS false +#define IORING_DEBUG_STATS false + +#if defined(_WIN32) || defined(_WIN64) +#define USE_REGISTERED_FILES false +#elif defined(__linux__) +#define USE_REGISTERED_FILES true +#endif + +#endif diff --git a/file_hasher.c b/file_hasher.c index 52e2c9b..3120723 100644 --- a/file_hasher.c +++ b/file_hasher.c @@ -82,17 +82,31 @@ int main(int argc, char **argv) { // Logical threads = CPU cores * 2 uint32_t cpu_threads = cpu_cores * 2; +#if MULTI_THREADED uint32_t num_scan_threads = cpu_threads; uint32_t num_hash_threads = cpu_threads; - // uint32_t num_hash_threads = 1; printf("%d cores %d threads CPU detected with %s instruction set\n" "Starting thread pool: %d scanning and %d hashing threads\n", cpu_cores, cpu_threads, get_xxhash_instruction_set(), num_scan_threads, num_hash_threads); +#else + uint32_t num_scan_threads = 1; + uint32_t num_hash_threads = 1; + + printf( + "%d cores %d threads CPU detected with %s instruction set\n" + "Starting thread pool: %d scanning and %d hashing threads(Debug mode)\n", + cpu_cores, cpu_threads, get_xxhash_instruction_set(), num_scan_threads, + num_hash_threads); + +#endif // Align IO Ring block size to the system page size +#if USE_IORING g_ioring_buffer_size = ALIGN_UP_POW2(g_ioring_buffer_size, g_pagesize); +#endif + // ------------------------------- // Scanning and hashing // ------------------------------- @@ -104,25 +118,6 @@ int main(int argc, char **argv) { MPMCQueue file_queue; mpmc_init(&file_queue, MiB(1)); - // Starting hash threads - // size_t num_hash_threads = num_threads; - // - // WorkerContext workers[num_hash_threads]; - // Thread *hash_threads = - // arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true); - // - // for (size_t i = 0; i < num_hash_threads; ++i) { - // workers[i].arena = arena_create(¶ms); - // workers[i].file_queue = &file_queue; - // - // if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) - // != - // 0) { - // fprintf(stderr, "Failed to create hash thread %zu\n", i); - // exit(1); - // } - // } - // Starting hash threads WorkerContext workers[num_hash_threads]; Thread *hash_threads = @@ -132,45 +127,19 @@ int main(int argc, char **argv) { workers[i].arena = arena_create(¶ms); workers[i].file_queue = &file_queue; +#if USE_IORING if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker_ioring, - &workers[i]) != 0) { + &workers[i]) != 0) +#else + if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) != + 0) +#endif + { fprintf(stderr, "Failed to create hash thread %zu\n", i); exit(1); } } - // Starting hash threads - // size_t num_hash_threads = num_threads; - // - // WorkerContext workers[num_hash_threads]; - // Thread *hash_threads = - // arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true); - // - // // Check if I/O Ring is available - // bool io_ring_available = false; - // HIORING test_ring = io_ring_init(); - // if (test_ring) { - // io_ring_available = true; - // io_ring_cleanup(test_ring); - // // printf("I/O Ring is available, using high-performance async I/O\n"); - // } else { - // printf("I/O Ring not available, using buffered I/O\n"); - // } - // - // for (size_t i = 0; i < num_hash_threads; ++i) { - // workers[i].arena = arena_create(¶ms); - // workers[i].file_queue = &file_queue; - // - // // Select the appropriate worker function - // ThreadFunc fn = io_ring_available ? (ThreadFunc)hash_worker_io_ring - // : (ThreadFunc)hash_worker; - // - // if (thread_create(&hash_threads[i], fn, &workers[i]) != 0) { - // fprintf(stderr, "Failed to create hash thread %zu\n", i); - // exit(1); - // } - // } - // Starting progress printing thread Thread progress_thread_handle; if (thread_create(&progress_thread_handle, (ThreadFunc)progress_thread, @@ -265,12 +234,14 @@ int main(int argc, char **argv) { // ------------------------------- // Print summary // ------------------------------- +#if USE_IORING uint64_t incomplete = atomic_load(&g_io_ring_fallbacks); if (incomplete > 0) { printf("\nWARNING: I/O Ring incomplete files: %llu (fallback to buffered " "I/O used)\n", (unsigned long long)incomplete); } +#endif double total_seconds = timer_elapsed(&total_timer); diff --git a/platform.c b/platform.c index deb0e63..809cdee 100644 --- a/platform.c +++ b/platform.c @@ -11,12 +11,7 @@ #define XXH_STATIC_LINKING_ONLY #include "xxh_x86dispatch.h" -// ----------------------------- Config ------------------------------------- -#define FILE_HASHES_TXT "file_hashes.txt" -#define HASH_STRLEN 33 // 128-bit hex (32 chars) + null -#define MAX_PATHLEN 4096 -#define READ_BLOCK (KiB(64)) - +#include "config.h" // ----------------------------- Globals ------------------------------------ static atomic_uint_fast64_t g_files_found = 0; static atomic_uint_fast64_t g_files_hashed = 0; @@ -24,7 +19,7 @@ static atomic_uint_fast64_t g_bytes_processed = 0; static atomic_int g_scan_done = 0; // ================== OS-agnostic functions abstraction ===================== -// ----------------------------- Timer functions -------------- +// --------------------- Timer functions --------------------- typedef struct { u64 start; u64 now; @@ -71,7 +66,7 @@ double timer_elapsed(HiResTimer *t) { #endif -// ----------------------------- Get HW info -------------- +// ------------------- Get HW info -------------------- #if defined(_WIN32) || defined(_WIN64) size_t platform_physical_cores(void) { @@ -367,7 +362,7 @@ static int parse_paths(char *line, char folders[][MAX_PATHLEN], return count; } -// ----------------------------- File time ------------------------- +// ------------------------- File time ------------------------- #if defined(_WIN32) || defined(_WIN64) static void format_time(uint64_t t, char *out, size_t out_sz) { if (t == 0) { @@ -382,7 +377,7 @@ static void format_time(uint64_t t, char *out, size_t out_sz) { strftime(out, out_sz, "%Y-%m-%d %H:%M:%S", &tm); } -// ----------------------------- Convert filetime to epoch -------------- +// ------------------ Convert filetime to epoch ------------------- static uint64_t filetime_to_epoch(const FILETIME *ft) { ULARGE_INTEGER ull; ull.LowPart = ft->dwLowDateTime; @@ -433,7 +428,7 @@ void platform_get_file_times(const char *path, uint64_t *out_created, #endif -// ----------------------------- File owner --------------------- +// -------------------- File owner --------------------- #if defined(_WIN32) || defined(_WIN64) static void get_file_owner(const char *path, char *out, size_t out_sz) { PSID sid = NULL; @@ -781,7 +776,7 @@ static void xxh3_hash_file_stream(const char *path, char *out_hex, // ------------------------- Hash worker -------------------------------- static THREAD_RETURN hash_worker(void *arg) { WorkerContext *ctx = (WorkerContext *)arg; - unsigned char *buf = (unsigned char *)malloc(READ_BLOCK); + void *buf = malloc(READ_BLOCK); for (;;) { FileEntry *fe = mpmc_pop(ctx->file_queue); @@ -885,31 +880,23 @@ static THREAD_RETURN progress_thread(void *arg) { return THREAD_RETURN_VALUE; } -// ======================== Hash worker IO Ring ======================== -// -------------------------- Configuration --------------------------- -#define IORING_BUFFER_SIZE (KiB(256)) -#define NUM_BUFFERS_PER_THREAD 32 -#define MAX_ACTIVE_FILES 32 -#define SUBMIT_TIMEOUT_MS 30000 -// #define IORING_DEBUG // Uncomment to print some errors +// ======================== IO Ring implemented ======================== +#if USE_IORING +// -------------------------- Data structures --------------------------- // Globals u64 g_ioring_buffer_size = 4096 * 64; static atomic_uint_fast64_t g_io_ring_fallbacks = 0; -// -------------------------- Data structures --------------------------- +#define IO_PENDING INT_MIN + +typedef struct IoBuffer IoBuffer; #if defined(_WIN32) || defined(_WIN64) // Windows I/O Ring types typedef HIORING IoRingHandle; #define BUILD_READ_RETURN_VALUE HRESULT -typedef struct { - HRESULT ResultCode; - uint32_t Information; - uintptr_t UserData; -} IoRingCQE; - #elif defined(__linux__) // Linux io_uring types typedef struct { @@ -923,16 +910,8 @@ typedef IoUring *IoRingHandle; typedef struct iovec IORING_BUFFER_INFO; #define BUILD_READ_RETURN_VALUE int -typedef struct { - int ResultCode; - uint32_t Information; - uintptr_t UserData; -} IoRingCQE; - #endif -typedef struct IoBuffer IoBuffer; - typedef struct FileReadContext { FileEntry *fe; uint64_t file_size; @@ -959,6 +938,10 @@ typedef struct FileReadContext { FileHandle file_handle; +#if USE_REGISTERED_FILES + uint32_t slot_id; +#endif + bool use_incremental_hash; bool completed; @@ -966,8 +949,6 @@ typedef struct FileReadContext { } FileReadContext; // -------------------------- Buffer structure --------------------------- -#define IO_PENDING INT_MIN - typedef struct IoBuffer { FileReadContext *file; void *data; @@ -985,8 +966,11 @@ typedef struct IoBuffer { // Thread-local I/O Ring context typedef struct ThreadIoContext { IoRingHandle ring; +#if defined(_WIN32) || defined(_WIN64) void *completion_event; - unsigned char *fallback_buffer; +#endif + + void *fallback_buffer; IoBuffer buffers[NUM_BUFFERS_PER_THREAD]; int buffer_pool[NUM_BUFFERS_PER_THREAD]; int free_count; @@ -998,6 +982,11 @@ typedef struct ThreadIoContext { bool use_registered_buffers; #endif +#if USE_REGISTERED_FILES + bool use_registered_files; + FileHandle registered_handles[MAX_ACTIVE_FILES]; +#endif + } ThreadIoContext; typedef struct { @@ -1006,7 +995,13 @@ typedef struct { uint32_t MaxVersion; } IoRingCapabilities; -// ----------------------------- Async I/O Abstraction ------------------------- +typedef struct { + BUILD_READ_RETURN_VALUE ResultCode; + uint32_t Information; + uintptr_t UserData; +} IoRingCQE; + +// ------------------------ IO Ring Abstraction ------------------------- #if defined(_WIN32) || defined(_WIN64) // Windows I/O Ring functions @@ -1063,35 +1058,48 @@ static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, return SUCCEEDED(hr) ? 0 : -1; } -static int ioring_register_buffers(ThreadIoContext *thread_ctx, - uint32_t num_buffers, - IORING_BUFFER_INFO *buf_info) { +static void ioring_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)) { char error_msg[256]; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + error_msg, sizeof(error_msg), NULL); - size_t size = FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), error_msg, - sizeof(error_msg), NULL); - - if (size > 0) { - fprintf(stderr, "Error registering buffers: %s (0x%08X)\n", error_msg, - (unsigned int)hr); - } else { - fprintf(stderr, "Error registering buffers: Unknown HRESULT (0x%08X)\n", - (unsigned int)hr); - } + fprintf(stderr, "Error registering buffers: %s (0x%08X)\n", error_msg, + (unsigned int)hr); } // Submit registration ioring_submit(thread_ctx, 0, 0, NULL); - - return hr; } +#if USE_REGISTERED_FILES +static void ioring_register_files(ThreadIoContext *thread_ctx) { + + HRESULT hr = BuildIoRingRegisterFileHandles( + thread_ctx->ring, MAX_ACTIVE_FILES, thread_ctx->registered_handles, + USERDATA_REGISTER); + + if (FAILED(hr)) { + char error_msg[256]; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + error_msg, sizeof(error_msg), NULL); + + fprintf(stderr, "WARNING: File registration failed: %s (0x%08X)\n", + error_msg, (unsigned int)hr); + } + + thread_ctx->use_registered_files = (hr == 0); +} +#endif + static void ioring_close_event(void *event) { CloseHandle(event); } static int close_ioring(ThreadIoContext *thread_ctx) { @@ -1107,9 +1115,23 @@ static BUILD_READ_RETURN_VALUE ioring_build_read(ThreadIoContext *thread_ctx, uint32_t buffer_id, size_t size, uint64_t offset, uintptr_t user_data) { + +#if USE_REGISTERED_FILES + IORING_HANDLE_REF file_ref; + + if (thread_ctx->use_registered_files) { + file_ref = (IORING_HANDLE_REF)IoRingHandleRefFromIndex(file_ctx->slot_id); + } else { + file_ref = + (IORING_HANDLE_REF)IoRingHandleRefFromHandle(file_ctx->file_handle); + } +#else IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(file_ctx->file_handle); +#endif + 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); @@ -1229,9 +1251,9 @@ static int create_ioring(ThreadIoContext *thread_ctx, uint32_t queue_size) { #define MAKE_BUF_INFO(a, l) \ (IORING_BUFFER_INFO) { .iov_base = (a), .iov_len = (size_t)(l) } -static int ioring_register_buffers(ThreadIoContext *thread_ctx, - uint32_t num_buffers, - IORING_BUFFER_INFO *buf_info) { +static void ioring_register_buffers(ThreadIoContext *thread_ctx, + uint32_t num_buffers, + IORING_BUFFER_INFO *buf_info) { IoUring *impl = (IoUring *)thread_ctx->ring; int hr = io_uring_register_buffers(&impl->ring, buf_info, num_buffers); @@ -1246,6 +1268,7 @@ static int ioring_register_buffers(ThreadIoContext *thread_ctx, "(ENOMEM).\n" "Increase the limit to solve this warning.\n"); + // TODO: document this in read me // The memlock limit in Linux restricts the amount of memory a process can // "lock" into physical RAM using the mlock() family of system calls. This // prevents the operating system from swapping that memory out to disk. @@ -1293,7 +1316,19 @@ static int ioring_register_buffers(ThreadIoContext *thread_ctx, } thread_ctx->use_registered_buffers = (hr == 0); - return hr == 0 ? 0 : -1; +} + +static void ioring_register_files(ThreadIoContext *thread_ctx) { + IoUring *impl = (IoUring *)thread_ctx->ring; + + int hr = io_uring_register_files(&impl->ring, thread_ctx->registered_handles, + MAX_ACTIVE_FILES); + if (hr < 0) { + fprintf(stderr, "file registeration failed: %s (code: %d)\n", strerror(-hr), + hr); + } + + thread_ctx->use_registered_files = (hr == 0); } static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, @@ -1435,10 +1470,14 @@ static FileReadContext *fq_push(FileQueue *fq) { if (fq->count == MAX_ACTIVE_FILES) return NULL; - FileReadContext *f = &fq->files[fq->tail]; + FileReadContext *file = &fq->files[fq->tail]; +#if USE_REGISTERED_FILES + file->slot_id = fq->tail; +#endif + fq->tail = (fq->tail + 1) % MAX_ACTIVE_FILES; fq->count++; - return f; + return file; } static FileReadContext *fq_peek_tail(FileQueue *fq) { @@ -1492,7 +1531,7 @@ static ThreadIoContext *ioring_init_thread(void) { } // Initialize buffer pool - thread_ctx->fallback_buffer = (unsigned char *)malloc(READ_BLOCK); + thread_ctx->fallback_buffer = malloc(READ_BLOCK); IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD]; @@ -1528,8 +1567,11 @@ static ThreadIoContext *ioring_init_thread(void) { thread_ctx->free_count = NUM_BUFFERS_PER_THREAD; // Register buffers - int hr = - ioring_register_buffers(thread_ctx, NUM_BUFFERS_PER_THREAD, buf_info); + ioring_register_buffers(thread_ctx, NUM_BUFFERS_PER_THREAD, buf_info); + +#if USE_REGISTERED_FILES + ioring_register_files(thread_ctx); +#endif thread_ctx->submitting = true; thread_ctx->num_submissions = 0; @@ -1579,7 +1621,7 @@ static void return_buffer(ThreadIoContext *ctx, IoBuffer *buf) { ctx->buffer_pool[ctx->free_count++] = buf->buffer_id; } -// -------------------------- Submit async read --------------------------- +// -------------------------- Build read --------------------------- static int build_read(ThreadIoContext *thread_ctx, FileReadContext *file_ctx, IoBuffer *buf, uint64_t offset, size_t size) { buf->offset = offset; @@ -1620,7 +1662,8 @@ static void process_completions(ThreadIoContext *thread_ctx, FileQueue *fq) { } // -------------------- File operations ----------------------- -static int init_file(FileReadContext *file, FileEntry *fe) { +static int init_file(ThreadIoContext *thread_ctx, FileReadContext *file, + FileEntry *fe) { memset(file, 0, sizeof(*file)); file->fe = fe; @@ -1631,12 +1674,19 @@ static int init_file(FileReadContext *file, FileEntry *fe) { if (file->file_handle == INVALID_FILE_HANDLE) { -#ifdef IORING_DEBUG +#if IORING_DEBUG_PRINTS printf("ERROR: Could not open file %s\n", fe->path); #endif return 0; } +#if (defined(_WIN32) || defined(_WIN64)) && USE_REGISTERED_FILES + if (thread_ctx->use_registered_files) { + thread_ctx->registered_handles[file->slot_id] = file->file_handle; + ioring_register_files(thread_ctx); + } +#endif + // Determine hash method based on file size if (file->file_size > g_ioring_buffer_size) { file->use_incremental_hash = true; @@ -1647,8 +1697,8 @@ static int init_file(FileReadContext *file, FileEntry *fe) { return 1; } -static void finalize_file(FileReadContext *file, ThreadIoContext *thread_ctx, - WorkerContext *worker_ctx) { +static void finalize_file(ThreadIoContext *thread_ctx, + WorkerContext *worker_ctx, FileReadContext *file) { FileEntry *fe = file->fe; @@ -1669,7 +1719,7 @@ static void finalize_file(FileReadContext *file, ThreadIoContext *thread_ctx, (unsigned long long)file->single_hash.low64); } } else { -#ifdef IORING_DEBUG +#if IORING_DEBUG_PRINTS printf("WARNING: Fallback for path: %s\n", fe->path); #endif @@ -1739,7 +1789,7 @@ static void hash_ready_files(ThreadIoContext *thread_ctx, FileQueue *fq, } else if (buf->bytes_read == 0 && IORING_SUCCEEDED(buf->result)) { file->reads_hashed++; // EOF } else { - finalize_file(file, thread_ctx, worker_ctx); + finalize_file(thread_ctx, worker_ctx, file); file->completed = true; } @@ -1750,7 +1800,7 @@ static void hash_ready_files(ThreadIoContext *thread_ctx, FileQueue *fq, if (!file->completed && file->active_reads == 0 && file->bytes_hashed >= file->file_size) { - finalize_file(file, thread_ctx, worker_ctx); + finalize_file(thread_ctx, worker_ctx, file); file->completed = true; thread_ctx->active_files--; } @@ -1770,7 +1820,7 @@ static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, for (;;) { - // ---------------- BUILD READS FOR CURRENT FILE ---------------- + // BUILD READS FOR CURRENT FILE if (file) { while (file->next_read_offset < file->file_size) { @@ -1807,7 +1857,7 @@ static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, if (IORING_FAILED(hr)) { // mark failure and stop this file return_buffer(thread_ctx, buf); - finalize_file(file, thread_ctx, worker_ctx); + finalize_file(thread_ctx, worker_ctx, file); file->completed = true; break; } @@ -1820,7 +1870,7 @@ static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, } } - // ---------------- ADD NEW FILE ---------------- + // ADD NEW FILE if (!thread_ctx->submitting) return; @@ -1835,8 +1885,8 @@ static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, FileReadContext *newfile = fq_push(fq); - if (!init_file(newfile, fe)) { - finalize_file(newfile, thread_ctx, worker_ctx); + if (!init_file(thread_ctx, newfile, fe)) { + finalize_file(thread_ctx, worker_ctx, newfile); newfile->completed = true; continue; } @@ -1845,6 +1895,7 @@ static void build_pending_reads(ThreadIoContext *thread_ctx, FileQueue *fq, thread_ctx->active_files++; } } + // -------------------------- Hash worker I/O Ring --------------------------- static THREAD_RETURN hash_worker_ioring(void *arg) { WorkerContext *worker_ctx = (WorkerContext *)arg; @@ -1877,12 +1928,12 @@ static THREAD_RETURN hash_worker_ioring(void *arg) { // Process completions process_completions(thread_ctx, &fq); - // debug - // printf( - // "Free buffers: %d, Submissions: %d, Active files: %d, fq count: - // %d\n", thread_ctx->free_count, thread_ctx->num_submissions, - // thread_ctx->active_files, fq.count); - // debug end +#if IORING_DEBUG_STATS + printf("Free buffers: %d, Submissions: %d, Active files: %d, fq count: + % d\n ", thread_ctx->free_count, thread_ctx->num_submissions, + thread_ctx->active_files, + fq.count); +#endif // Hash files hash_ready_files(thread_ctx, &fq, worker_ctx); @@ -1897,3 +1948,4 @@ static THREAD_RETURN hash_worker_ioring(void *arg) { ioring_cleanup_thread(thread_ctx); return THREAD_RETURN_VALUE; } +#endif From b4487cd3a6e8eeed921e49001006efd53c834971 Mon Sep 17 00:00:00 2001 From: amir Date: Tue, 28 Apr 2026 17:52:02 +0100 Subject: [PATCH 7/7] Finalizing the implementation of file registration Adding the file system check in Linux(can be enabled from the config file) Adding a more options to the config file Writing the README --- .gitignore | 1 + README.md | 229 ++++++++++++- base.h | 1 + binaries/changelog.txt | 4 +- compile_commands.json | 7 - config.h | 28 +- file_hasher.c | 2 +- io_uring_test | Bin 27808 -> 0 bytes io_uring_test.c | 461 ++++++++++++--------------- platform.c | 708 ++++++++++++++++++++++++++--------------- 10 files changed, 879 insertions(+), 562 deletions(-) delete mode 100644 compile_commands.json delete mode 100644 io_uring_test diff --git a/.gitignore b/.gitignore index 91ce0c9..670e490 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ temp_code.c /file_hasher /io_uring_test /file_hasher +/io_uring_test diff --git a/README.md b/README.md index 719ef96..1b6353e 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,221 @@ # filehasher -Collects some metadata and hashes files. +## Presentation +Collects some metadata and hashes files. It outputs the path, hash, size, creation and +last modification dates and the author in file_hasher.txt. +Creation and modification dates and author can be disabled in the config file. -## Building: -### Windows: -#### Release: -clang-cl /O3 file_hasher.c xxh_x86dispatch.c +It is a high performance cross platform Windows and Linux compatible program, it uses: + * Multiple threads for scanning and hashing (multi-threading can be disabled in the config file). + * Stores the generated data in thread local configurable arenas that support growing + by committing more memory and chaining blocks. + * Two Multi Producer Multi Consumer queues, one for the scanners and one between the scanners and hashers. + * xxh3_128bits algorithm from xxhash, that supports SIMD instruction sets (SSE2, AVX2, AVX512) + and uses a runtime dispatcher to select the best available instruction set. + * IO Ring for asynchronous I/O in Windows and the equivalent io_uring in Linux. + The implementation is event driven, thread local, uses DMA and direct disk I/O, + bypassing the OS cache completely, registered buffers (and registered files in io_uring), + it supports bashing multiple submissions and can handle multiple files at the same time. + It can be disabled in the config file. + * Fallback to buffered I/O if there is errors in the IO Ring path. -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 +## Building +### Windows +#### Release -#### Debug: -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 +**Note**: Make sur to use UCRT64 environment from MSYS2 instead of the standard MinGW environment. +UCRT64 uses the modern Universal C Runtime (ucrtbase.dll), which supports the newest APIs, +the standard MSYS2 uses the legacy msvcrt.dll and does not support IO Ring. +To install: +pacman -S mingw-w64-ucrt-x86_64-gcc +pacman -S mingw-w64-ucrt-x86_64-clang +pacman -Syu +And add to path: +C:\msys64\ucrt64\bin -### Linux: -#### Release: -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 -o file_hasher +clang -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher +clang-cl /O2 file_hasher.c xxhash.c xxh_x86dispatch.c + +#### Debug +gcc -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher +clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher +clang-cl /Zi /Od file_hasher.c xxhash.c xxh_x86dispatch.c + +### Linux +#### Release gcc -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher +clang -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher -#### Debug: -clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher +#### Debug gcc -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher +clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher + +## Notes about the IO Ring implementations +### IO Ring + +#### File registration +Registering files is a performance optimization that allows the kernel to allocate an array +of descriptors/handles to pre-validate and maintain long-term references to file handles. +Instead of passing a standard file descriptor/handle with every I/O request, you pass a simple integer +index into a pre-registered table. + +The Linux implementation has io_uring_register_files_scarse() to create an empty array of descriptors +(initialized with -1) without having to create and initialize it in the user space, and we can +use io_uring_register_files_update() to update one or more entries. Windows on the other hand +is limited to BuildIoRingRegisterFileHandles() only, so we need to re register the entire array of handles +each time. This is why there is a provided macro in config.h to disable or enable it. + +##### Why Register Files? (The Benefits) +When you use a standard file descriptor in a high-frequency I/O loop, +the kernel must perform several "hidden" tasks for every single operation: + * Permission Checks: Validating that the process still has the right to read/write + that specific file. + * Reference Counting: Incrementing the file's internal reference count at the start of + the I/O and decrementing it at the end to ensure the file isn't closed while in use. + * Object Lookup: Traversing the internal "file descriptor table" to find the actual + kernel object associated with your integer ID. + +Registering the files performs these checks once at registration time. Subsequent +I/O operations skip these steps, significantly reducing CPU overhead and latency, +especially when handling thousands of small I/O operations per second. + +##### Comparison: Linux vs. Windows Implementation +While both systems share the same core concept, their APIs and management styles differ significantly. +Feature Linux (io_uring) Windows (IoRing) +API Call io_uring_register BuildIoRingRegisterFileHandles +Registration Method Synchronous system call that blocks until the table is set up. Asynchronous request submitted to the ring just like a read/write operation. +Partial Updates Supports IORING_REGISTER_FILES_UPDATE to swap specific indices without a full reset. Does not support partial updates; a new registration call replaces the entire existing table. +Memory Mapping User must manually mmap() the queues into their address space. The kernel handles memory mapping automatically when the ring is created. +Scope of Operations Extremely broad (files, sockets, timers, signals, even other rings). Primarily focused on file storage (read, write, flush). + +#### Completion Wait count +To avoid busy waiting when receiving CQEs, we can use io_uring_submit_and_wait() in Linux by entering a wait count, +the threads sleeps until the count of CQEs are received, in windows the wait_count is present in SubmitIoRing() +but is not implemented yet, so we wait with a completion event for a single completion. Another limitation on the completion +event is that the kernel will waik up the thread only when receiving the first CQE, after that we need to drain the completion +queue completely before sleeping again, or we enter an eternal slumber. And my config, each time the thread wakes up +it receives rarely more than 3 to 5 CQEs and most of the time only one CQE. + +#### Filtering CQEs + +Unlike Linux, The Windows implementation treats buffer and file registration +as an asynchronous operation that we submit to the ring, similar to a read or write. +Those operations produce CQEs (completion queue entries) that we filter here using +cqe.UserData == USERDATA_REGISTER +```c + if (win_cqe.UserData == USERDATA_REGISTER) + continue; +``` + +### io_uring + +#### Creation flags +io_uring provides a lot of configuration flags compared to IO Ring, some +of them are at the creation and others during the operations, here what +we use in this implementation at creation time and is lacking in the +IO Ring implementation. + + * IORING_SETUP_SINGLE_ISSUER: Since we are using a thread local io_uring, we can + set this flag to remove the atomic operations. + * IORING_SETUP_DEFER_TASKRUN: By default, the kernel sends an interrupts when a CQE + is ready, we use this flag to disable this syscall and wait for a specific number of + CQEs to be ready to group them, this reduces the number of syscall. + +#### Memlock limit warning + +```c + "WARNING: Buffer registration failed due to memlock limits (ENOMEM).\n" + "Increase the limit to solve this warning.\n"); +``` + +The Memlock limit in Linux restricts the amount of memory a process can +"lock" into physical RAM using the mlock() family of system calls. This +prevents the operating system from swapping that memory out to disk. +And registering buffers will lock the buffers memory so the hardware +can access it directly without kernel intervention and prevents the kernel from +swapping it to the SSD or HDD. Increase the limit to be able to register the buffers. + +##### Modifying the Limit: +The method for changing the memlock limit depends on whether you are +managing a user session or a system service. +1. For Users and Interactive Sessions +To permanently increase the limit for a specific user or group, modify +the /etc/security/limits.conf file. Add the following lines: + +```conf + # Example for a specific user (replace 'username'), unlimited or a custom value in KB + username soft memlock unlimited + username hard memlock unlimited + + # Example for all users + * soft memlock unlimited + * hard memlock unlimited +``` + +Soft Limit: The value the user starts with; can be raised up to the +hard limit. + +Hard Limit: The absolute maximum; only a privileged user +(root) can increase this. Values: Can be set in Kilobytes (KB) or as +unlimited. + +2. For Systemd Services +Settings in limits.conf do not affect background services managed by +systemd. To increase the limit for a service, edit its service file +(e.g., /etc/systemd/system/myservice.service) and add: +```conf + [Service] + LimitMEMLOCK=infinity +``` + +##### Why Register Buffers? +In a standard "unregistered" I/O operation, the kernel must perform several +expensive steps for every single read or write: + * Virtual-to-Physical Mapping: The kernel has to translate your application's + virtual memory addresses into physical RAM addresses. + * Page Pinning: The kernel must "pin" the memory pages (using get_user_pages) + to prevent them from being swapped to disk or moved while the hardware + (like your SSD) is writing to them. + * TLB Overhead: Constant mapping and unmapping put pressure on the Translation + Lookaside Buffer (TLB), which can slow down the CPU. + +Registering the buffers performs all of this "pinning" and "mapping" once. + +#### Direct I/O: O_DIRECT (Linux) and FILE_FLAG_NO_BUFFERING (Windows) + +Modern operating systems normally use a page cache when reading files. This means file +data is first loaded into kernel memory and then copied to user space. While this improves +performance for many workloads, it introduces extra memory usage and copy overhead. + +Both Linux and Windows provide a way to bypass this cache and perform direct I/O: + +Linux: O_DIRECT +Windows: FILE_FLAG_NO_BUFFERING + +These flags instruct the OS to transfer data directly between disk and user-provided buffers, avoiding the page cache. + +##### Benefits +1. Reduced memory overhead +Avoids polluting the OS page cache +Especially useful for large sequential reads (e.g. hashing, backups) +2. Lower CPU usage +Eliminates extra memory copies between kernel and user space +3. Predictable performance +No interference from cache eviction or readahead heuristics +More consistent throughput for streaming workloads +4. Better scalability +Ideal for high-throughput, multi-threaded I/O pipelines +Prevents cache contention between threads +5. Avoids double caching +Important when the application already manages its own buffering + +##### File system compatibility +Not all file systems are compatible with O_DIRECT, if we try to open files residing in an NTFS partition, +most of the time it will fail, and some times it opens but the CQEs return with an error code bad +descriptor, and it causes some lags. + +To address this issue the program falls back to sequential read when the open fails, and falls back to +buffered sequential hashing if we receive an error in the CQEs. There is also a file system detection +that we can enable in the config file, it will enable the collection of the file system in scan_folder() +and the file will be opened accordingly, but it costs one additional syscall / directory. diff --git a/base.h b/base.h index 3d8e91a..0b8e4b0 100644 --- a/base.h +++ b/base.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #endif diff --git a/binaries/changelog.txt b/binaries/changelog.txt index e619f7e..ea0ee1c 100644 --- a/binaries/changelog.txt +++ b/binaries/changelog.txt @@ -14,7 +14,7 @@ v3.2: Making the lock free MPMC queue growable Add padding to avoir false sharing Add sleep() and SwitchToThread() to limit spinning -v3.3: Fix bug slots used before initialization,compare and swap is protecting updating committed, but it is not protecting the memory initialization. Adding atomic_flag commit_lock to protect against that +v3.3: Fix bug slots used before initialization, compare and swap is protecting updating committed, but it is not protecting the memory initialization. Adding atomic_flag commit_lock to protect against that Fix bug multiple threads committing at the same time, fixed by using atomic_flag commit_lock and re-checking committed after acquiring the lock Reorder helper functions @@ -55,4 +55,4 @@ Hashing small files using XXH3_128bits() instead of the streaming pipeline(XXH3_ fixing the xxh_x86dispatch warnings Updating the progress printing function Implementing a config file - +Writing the README file diff --git a/compile_commands.json b/compile_commands.json deleted file mode 100644 index 0b29205..0000000 --- a/compile_commands.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "directory": "D:/Code/c/filehasher", - "command": "clang-cl /O2 file_hasher.c xxh_x86dispatch.c", - "file": "file_hasher.c" - } -] diff --git a/config.h b/config.h index 3aa7208..5cd6f04 100644 --- a/config.h +++ b/config.h @@ -1,27 +1,31 @@ #define FILE_HASHES_TXT "file_hashes.txt" -#define HASH_STRLEN 33 // 128-bit hex (32 chars) + null -#define MAX_PATHLEN 4096 -#define READ_BLOCK (KiB(64)) -#define MULTI_THREADED true +// Metadata selection +#define FILE_TIMES 1 // created and modified time +#define FILE_OWNER 1 + +#define MULTI_THREADING 1 +#define READ_BLOCK KiB(64) // -------------------- IO Ring Configuration ---------------------- #define USE_IORING 1 #if USE_IORING -#define IORING_BUFFER_SIZE (KiB(256)) +#define IORING_BUFFER_SIZE KiB(256) #define NUM_BUFFERS_PER_THREAD 32 -#define MAX_ACTIVE_FILES 32 -#define SUBMIT_TIMEOUT_MS 30000 +#define MAX_ACTIVE_FILES 16 -#define IORING_DEBUG_PRINTS false -#define IORING_DEBUG_STATS false +#define SUBMIT_TIMEOUT_MS 10000 +#define IORING_DEBUG_PRINTS 0 +#define IORING_DEBUG_STATS 0 #if defined(_WIN32) || defined(_WIN64) -#define USE_REGISTERED_FILES false +#define USE_REGISTERED_FILES 1 + #elif defined(__linux__) -#define USE_REGISTERED_FILES true -#endif +#define USE_REGISTERED_FILES 1 +#define CHECK_FILE_SYSTEM 0 #endif +#endif diff --git a/file_hasher.c b/file_hasher.c index 3120723..67bf97d 100644 --- a/file_hasher.c +++ b/file_hasher.c @@ -82,7 +82,7 @@ int main(int argc, char **argv) { // Logical threads = CPU cores * 2 uint32_t cpu_threads = cpu_cores * 2; -#if MULTI_THREADED +#if MULTI_THREADING uint32_t num_scan_threads = cpu_threads; uint32_t num_hash_threads = cpu_threads; diff --git a/io_uring_test b/io_uring_test deleted file mode 100644 index 6315aaab05dd61e1fb713218a40b7939f688c2a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27808 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 YxmuCFql{!fI-oS@-&=7Wd93lj0qbTVjsO4v diff --git a/io_uring_test.c b/io_uring_test.c index 735a44d..82b7d54 100644 --- a/io_uring_test.c +++ b/io_uring_test.c @@ -17,9 +17,9 @@ gcc -o io_uring_test io_uring_test.c -luring #include #include -#define TEST_FILE "test_io_uring.txt" #define BUFFER_SIZE 4096 #define NUM_BUFFERS 4 +#define NUM_REGISTERED_FILES 3 // Test with 3 files // Colors for output #define COLOR_GREEN "\033[0;32m" @@ -50,25 +50,17 @@ 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"); +static int create_test_file(const char *filename, const char *content) { + FILE *f = fopen(filename, "w"); if (!f) { perror("Failed to create test file"); return -1; } - fprintf(f, "%s", test_content); + fprintf(f, "%s", content); fclose(f); - print_info("Test file created successfully"); + printf(" Created test file: %s\n", filename); return 0; } @@ -93,16 +85,14 @@ 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 + *buffers = aligned_alloc(4096, total_size); 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; @@ -121,334 +111,287 @@ static int test_register_buffers(struct io_uring *ring, void **buffers, return 0; } -// Test 3: Open file -// Modified test_open_file function -static int test_open_file(int *fd, TestResults *results) { - print_step("File opening"); +// Test 3: Register files sparse (empty table) +static int test_register_files_sparse(struct io_uring *ring, unsigned nr_files, + TestResults *results) { + print_step("Sparse file registration (empty table)"); - // Get file size - struct stat st; - if (stat(TEST_FILE, &st) != 0) { - print_failure("stat", strerror(errno)); - results->failed++; - return -1; - } + int ret = io_uring_register_files_sparse(ring, nr_files); + if (ret < 0) { + if (ret == -EINVAL) { + print_info( + "io_uring_register_files_sparse not supported (kernel < 5.19)"); + print_info("Trying regular file registration with invalid fds..."); - // Check if file size is page-aligned - int page_size = plat_get_pagesize(); - size_t file_size = st.st_size; + // Fallback: register with invalid fds + int *invalid_fds = calloc(nr_files, sizeof(int)); + if (!invalid_fds) { + print_failure("Allocating invalid fds array", "Out of memory"); + results->failed++; + return -1; + } - printf(" File size: %zu bytes\n", file_size); - printf(" Page size: %d bytes\n", page_size); + for (int i = 0; i < nr_files; i++) { + invalid_fds[i] = -1; // Mark all as invalid + } - 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)); - } + ret = io_uring_register_files(ring, invalid_fds, nr_files); + free(invalid_fds); - // 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)); + if (ret < 0) { + print_failure("Regular file registration also failed", strerror(-ret)); + results->failed++; + return -1; + } + print_success("File table registered (regular, with invalid fds)"); + } else { + print_failure("io_uring_register_files_sparse", strerror(-ret)); results->failed++; return -1; } - print_info("Using buffered I/O (O_DIRECT not available)"); } else { - print_success("File opened with O_DIRECT"); + printf(" Registered empty file table with %u slots\n", nr_files); + print_success("Sparse file table created"); } 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"); +// Test 4: Update file slot and read from it +static int test_file_read_loop(struct io_uring *ring, struct iovec *iovs, + const char **filenames, + const char **expected_contents, int num_files, + TestResults *results) { + print_step("File slot update and read loop"); - // Get file size for proper alignment - struct stat st; - if (fstat(fd, &st) != 0) { - print_failure("fstat", strerror(errno)); + int *fds = calloc(num_files, sizeof(int)); + if (!fds) { + print_failure("Allocating fd array", "Out of memory"); 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); + // Open all files first + for (int i = 0; i < num_files; i++) { + fds[i] = open(filenames[i], O_RDONLY); + if (fds[i] < 0) { + print_failure("Opening file", filenames[i]); + results->failed++; + // Close already opened files + for (int j = 0; j < i; j++) + close(fds[j]); + free(fds); + return -1; + } + printf(" Opened %s (fd=%d)\n", filenames[i], fds[i]); } - 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; - } + // Test loop: update slot, submit read, verify + for (int slot = 0; slot < num_files; slot++) { + printf("\n --- Testing slot %d with file '%s' ---\n", slot, + filenames[slot]); - // 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); + // Update the file registration for this slot + printf(" Updating slot %d with fd %d...\n", slot, fds[slot]); + int ret = io_uring_register_files_update(ring, slot, &fds[slot], 1); - int ret = io_uring_submit(ring); - if (ret < 0) { - print_failure("io_uring_submit", strerror(-ret)); - results->failed++; - return -1; - } + if (ret < 0) { + print_failure("File registration update", strerror(-ret)); + results->failed++; + continue; + } + printf(" Slot update result: %d (expected 1)\n", ret); - print_success("Read operation submitted successfully"); - results->passed++; - return 0; -} + // Get file size for read size calculation + struct stat st; + if (fstat(fds[slot], &st) != 0) { + print_failure("fstat", strerror(errno)); + results->failed++; + continue; + } -// 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"); + size_t file_size = st.st_size; + size_t read_size = BUFFER_SIZE; - int ret = io_uring_wait_cqe(ring, cqe); - if (ret < 0) { - print_failure("io_uring_wait_cqe", strerror(-ret)); - results->failed++; - return -1; - } + // Adjust read size for O_DIRECT if needed + int page_size = plat_get_pagesize(); + if (read_size > file_size) { + read_size = ALIGN_UP_POW2(file_size, page_size); + } - print_success("Completion received"); - results->passed++; - return 0; -} + printf(" File size: %zu, read size: %zu\n", file_size, read_size); -// 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"); + // Clear buffer for this test + memset(iovs[0].iov_base, 0, BUFFER_SIZE); - 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++) { + // Submit read using registered file struct io_uring_sqe *sqe = io_uring_get_sqe(ring); if (!sqe) { - print_failure("Getting SQE for concurrent read", "No available SQE"); + print_failure("Getting SQE", "No available SQE"); results->failed++; - return -1; + continue; } - 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++; - } + // Use slot index with fixed file flag + io_uring_prep_read_fixed(sqe, slot, iovs[0].iov_base, read_size, 0, 0); + sqe->flags |= IOSQE_FIXED_FILE; + io_uring_sqe_set_data64(sqe, 100 + slot); // Unique user_data per slot - int ret = io_uring_submit(ring); - if (ret != submitted) { - char msg[64]; - snprintf(msg, sizeof(msg), "Expected %d, got %d", submitted, ret); + ret = io_uring_submit(ring); + if (ret < 0) { + print_failure("Submitting read", strerror(-ret)); + results->failed++; + continue; + } + printf(" Submitted read (1 SQE)\n"); - 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++) { + // Wait for completion struct io_uring_cqe *cqe; ret = io_uring_wait_cqe(ring, &cqe); if (ret < 0) { - print_failure("Waiting for concurrent read completion", strerror(-ret)); + print_failure("Waiting for completion", strerror(-ret)); results->failed++; - return -1; + continue; } + // Process completion uint64_t user_data = io_uring_cqe_get_data64(cqe); - int res = cqe->res; + int bytes_read = cqe->res; + + printf(" Completion: user_data=%lu, result=%d\n", (unsigned long)user_data, + bytes_read); + + if (bytes_read < 0) { + print_failure("Read operation", strerror(-bytes_read)); + results->failed++; + io_uring_cqe_seen(ring, cqe); + continue; + } + + if (user_data != 100 + slot) { + print_failure("User data mismatch", "Wrong user_data value"); + results->failed++; + io_uring_cqe_seen(ring, cqe); + continue; + } + + // Verify the data + char *data = (char *)iovs[0].iov_base; + printf(" Data read (%d bytes): %.*s\n", bytes_read, + bytes_read < 100 ? bytes_read : 100, data); + + if (strstr(data, expected_contents[slot]) == NULL) { + print_failure("Data verification", + "Expected content not found in read data"); + results->failed++; + } else { + print_success("Data verified successfully"); + results->passed++; + } - printf(" Concurrent read %lu completed: %d bytes read\n", user_data, res); io_uring_cqe_seen(ring, cqe); + + // Invalidate the slot after use (mark as -1) + int invalid_fd = -1; + ret = io_uring_register_files_update(ring, slot, &invalid_fd, 1); + if (ret < 0) { + printf(" Warning: Could not invalidate slot %d: %s\n", slot, + strerror(-ret)); + } } - print_success("Concurrent reads completed successfully"); - results->passed++; + // Close all files + for (int i = 0; i < num_files; i++) { + if (fds[i] >= 0) + close(fds[i]); + } + free(fds); + 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(" io_uring Sparse File Registration Test\n"); printf("========================================\n" COLOR_RESET); - // Create test file - if (create_test_file() != 0) { - return 1; + // Define test files and their content + const char *filenames[] = {"test_file_0.txt", "test_file_1.txt", + "test_file_2.txt"}; + + const char *contents[] = { + "This is file 0: Hello World! The quick brown fox jumps over the lazy " + "dog.", + "This is file 1: io_uring is awesome for async I/O operations!", + "This is file 2: Testing sparse file registration with multiple files."}; + + const char *expected_substrings[] = {"Hello World", "io_uring is awesome", + "sparse file registration"}; + + int num_files = 3; + + // Create all test files + print_info("Creating test files..."); + for (int i = 0; i < num_files; i++) { + if (create_test_file(filenames[i], contents[i]) != 0) { + return 1; + } } // Test 1: Create io_uring if (test_io_uring_create(&ring, &results) != 0) { - cleanup(&ring, fd, buffers); - return 1; + goto cleanup_files; } // Test 2: Register buffers if (test_register_buffers(&ring, &buffers, iovs, &results) != 0) { - cleanup(&ring, fd, buffers); - return 1; + io_uring_queue_exit(&ring); + goto cleanup_files; } - // Test 3: Open file - if (test_open_file(&fd, &results) != 0) { - cleanup(&ring, fd, buffers); - return 1; + // Test 3: Register empty file table (sparse) + if (test_register_files_sparse(&ring, num_files, &results) != 0) { + io_uring_unregister_buffers(&ring); + free(buffers); + io_uring_queue_exit(&ring); + goto cleanup_files; } - // 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 4: Loop through files, update slots, read and verify + test_file_read_loop(&ring, iovs, filenames, expected_substrings, num_files, + &results); - // Test 5: Wait for completion - struct io_uring_cqe *cqe; - if (test_wait_completion(&ring, &cqe, &results) != 0) { - cleanup(&ring, fd, buffers); - return 1; - } + // Cleanup + io_uring_unregister_files(&ring); + io_uring_unregister_buffers(&ring); + free(buffers); + io_uring_queue_exit(&ring); - // 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; +cleanup_files: + // Remove test files + for (int i = 0; i < num_files; i++) { + remove(filenames[i]); } // Print summary + int total = results.passed + results.failed; printf(COLOR_BLUE "\n========================================\n"); printf(" TEST SUMMARY\n"); printf("========================================\n" COLOR_RESET); - printf(" Total tests: %d\n", results.passed + results.failed); + printf(" Total tests: %d\n", total); printf(COLOR_GREEN " Passed: %d\n" COLOR_RESET, results.passed); if (results.failed > 0) { printf(COLOR_RED " Failed: %d\n" COLOR_RESET, results.failed); + printf(COLOR_RED "\n ✗ SOME TESTS FAILED!\n" COLOR_RESET); } else { - printf(COLOR_GREEN " ✓ ALL TESTS PASSED!\n" COLOR_RESET); + printf(COLOR_GREEN "\n ✓ 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 809cdee..7c5b802 100644 --- a/platform.c +++ b/platform.c @@ -18,6 +18,8 @@ static atomic_uint_fast64_t g_files_hashed = 0; static atomic_uint_fast64_t g_bytes_processed = 0; static atomic_int g_scan_done = 0; +#define HASH_STRLEN 33 // 128-bit hex (32 chars) + null terminator +#define MAX_PATHLEN KiB(4) // ================== OS-agnostic functions abstraction ===================== // --------------------- Timer functions --------------------- typedef struct { @@ -176,13 +178,13 @@ static void os_file_close(FileHandle handle) { close(handle); } // -------------------- Thread abstraction ------------------- // Threads context typedef struct { - u8 num_threads; - mem_arena *path_arena; mem_arena *meta_arena; MPMCQueue *dir_queue; MPMCQueue *file_queue; + + u8 num_threads; } ScannerContext; typedef struct { @@ -363,6 +365,7 @@ static int parse_paths(char *line, char folders[][MAX_PATHLEN], } // ------------------------- File time ------------------------- +#if FILE_TIMES #if defined(_WIN32) || defined(_WIN64) static void format_time(uint64_t t, char *out, size_t out_sz) { if (t == 0) { @@ -426,11 +429,24 @@ void platform_get_file_times(const char *path, uint64_t *out_created, } } +static int platform_get_file_times_fd(int dir_fd, const char *name, + 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 + *modified = st.st_mtime; + return 0; + } + return -1; +} +#endif #endif // -------------------- File owner --------------------- #if defined(_WIN32) || defined(_WIN64) -static void get_file_owner(const char *path, char *out, size_t out_sz) { +#if FILE_OWNER +void platform_get_file_owner(const char *path, char *out_owner, + size_t out_owner_size) { PSID sid = NULL; PSECURITY_DESCRIPTOR sd = NULL; @@ -444,43 +460,159 @@ static void get_file_owner(const char *path, char *out, size_t out_sz) { if (LookupAccountSidA(NULL, sid, name, &name_len, domain, &domain_len, &use)) { - snprintf(out, out_sz, "%s\\%s", domain, name); + snprintf(out_owner, out_owner_size, "%s\\%s", domain, name); } else { - snprintf(out, out_sz, "UNKNOWN"); + snprintf(out_owner, out_owner_size, "UNKNOWN"); } } else { - snprintf(out, out_sz, "UNKNOWN"); + snprintf(out_owner, out_owner_size, "UNKNOWN"); } if (sd) LocalFree(sd); } - -void platform_get_file_owner(const char *path, char *out_owner, - size_t out_owner_size) { - get_file_owner(path, out_owner, out_owner_size); -} +#endif #elif defined(__linux__) -static void get_file_owner(uid_t uid, char *out, size_t out_sz) { - struct passwd *pw = getpwuid(uid); - if (pw) { - snprintf(out, out_sz, "%s", pw->pw_name); - } else { - snprintf(out, out_sz, "UNKNOWN"); - } -} - +#if FILE_OWNER void platform_get_file_owner(const char *path, char *out_owner, size_t out_owner_size) { struct stat st; + const char *owner = "UNKNOWN"; + if (stat(path, &st) == 0) { - get_file_owner(st.st_uid, out_owner, out_owner_size); - } else { - snprintf(out_owner, out_owner_size, "UNKNOWN"); + struct passwd *pw = getpwuid(st.st_uid); + if (pw) { + owner = pw->pw_name; + } + } + + snprintf(out_owner, out_owner_size, "%s", owner); +} + +static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, + size_t owner_size) { + struct stat st; + if (fstatat(dir_fd, name, &st, 0) == 0) { + struct passwd pw; + struct passwd *result; + char buffer[4096]; // Sufficiently large buffer for passwd data + + // Reentrant version (thread-safe) + if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 && + result != NULL && result->pw_name != NULL) { + strncpy(owner, result->pw_name, owner_size - 1); + owner[owner_size - 1] = '\0'; + } else { + // Fallback to uid + snprintf(owner, owner_size, "uid:%d", st.st_uid); + } + return 0; + } + return -1; +} +#endif + +// ----------------------------- File system ----------------------------- + +#if CHECK_FILE_SYSTEM +typedef enum FileSystemType { + FS_UNKNOWN = 0, + FS_EXT4, + FS_XFS, + FS_BTRFS, + FS_TMPFS, + FS_NFS, + FS_CIFS, + FS_FAT, + FS_EXFAT, + FS_NTFS, + FS_ZFS, + FS_F2FS, + FS_EROFS, + FS_VIRTIOFS, + FS_OVERLAY, + FS_HUGETLBFS, + FS_SQUASHFS, + FS_PROC, + FS_SYSFS, +} FileSystemType; + +static inline FileSystemType fs_from_magic(long type) { + switch (type) { + case 0xEF53: + return FS_EXT4; + case 0x58465342: + return FS_XFS; + case 0x9123683E: + return FS_BTRFS; + case 0x01021994: + return FS_TMPFS; + case 0x6969: + return FS_NFS; + case 0xFF534D42: + return FS_CIFS; + case 0x4d44: + return FS_FAT; + case 0x2011BAB0: + return FS_EXFAT; + case 0x5346544E: + return FS_NTFS; + case 0x2FC1211: + return FS_ZFS; + case 0xF2F52010: + return FS_F2FS; + case 0xE0F5E1E2: + return FS_EROFS; + case 0x56495254: + return FS_VIRTIOFS; + case 0x794C764F: + return FS_OVERLAY; + case 0x958458f6: + return FS_HUGETLBFS; + case 0x73717368: + return FS_SQUASHFS; + case 0x9fa0: + return FS_PROC; + case 0x62656572: + return FS_SYSFS; + + default: + return FS_UNKNOWN; } } +// Yes it is officially called "magic number" or "signature" in the +// documentation + +typedef enum { + FS_POLICY_BUFFERED, + FS_POLICY_DIRECT_OK, +} FsPolicy; + +static inline FsPolicy fs_policy(FileSystemType fs) { + switch (fs) { + case FS_EXT4: + case FS_XFS: + case FS_BTRFS: + case FS_ZFS: + case FS_F2FS: + case FS_NFS: + case FS_CIFS: + case FS_VIRTIOFS: + return FS_POLICY_DIRECT_OK; + + case FS_TMPFS: // Resides in Page Cache; O_DIRECT generally returns EINVAL + case FS_EROFS: // Read-only filesystem; O_DIRECT support is + // uncommon/restricted + case FS_FAT: // Generally does not implement direct_IO address space ops + case FS_EXFAT: // Limited support depending on driver implementation + case FS_NTFS: // Depends on driver (ntfs3 supports it, older ntfs-3g does not) + default: + return FS_POLICY_BUFFERED; + } +} +#endif #endif // ----------------------------- Scan helpers ----------------------------- @@ -488,9 +620,17 @@ typedef struct FileEntry { char *path; uint64_t size_bytes; +#if FILE_TIMES uint64_t created_time; // epoch uint64_t modified_time; // epoch seconds - char owner[128]; // resolved owner name +#endif +#if FILE_OWNER + char owner[128]; // resolved owner name +#endif + +#if CHECK_FILE_SYSTEM // Linux only + FileSystemType fs_type; +#endif } FileEntry; typedef struct { @@ -560,10 +700,12 @@ void scan_folder(const char *base, ScannerContext *ctx) { size_t name_len = strlen(fd.cFileName); path_builder_set_filename(&pb, fd.cFileName, name_len); + // If it's a directory: if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { char *dir = path_builder_dup_arena(&pb, ctx->path_arena, false); mpmc_push_work(ctx->dir_queue, dir); } else { + // else a file: atomic_fetch_add(&g_files_found, 1); FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); @@ -577,8 +719,14 @@ void scan_folder(const char *base, ScannerContext *ctx) { fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false); strcpy(fe->path, temp_path); +#if FILE_TIMES platform_get_file_times(pb.buffer, &fe->created_time, &fe->modified_time); +#endif + +#if FILE_OWNER platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner)); +#endif + fe->size_bytes = ((uint64_t)fd.nFileSizeHigh << 32) | fd.nFileSizeLow; mpmc_push(ctx->file_queue, fe); @@ -590,39 +738,6 @@ void scan_folder(const char *base, ScannerContext *ctx) { } #elif defined(__linux__) -static int platform_get_file_times_fd(int dir_fd, const char *name, - 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 - *modified = st.st_mtime; - return 0; - } - return -1; -} - -static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, - size_t owner_size) { - struct stat st; - if (fstatat(dir_fd, name, &st, 0) == 0) { - struct passwd pw; - struct passwd *result; - char buffer[4096]; // Sufficiently large buffer for passwd data - - // Reentrant version (thread-safe) - if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 && - result != NULL && result->pw_name != NULL) { - strncpy(owner, result->pw_name, owner_size - 1); - owner[owner_size - 1] = '\0'; - } else { - // Fallback to uid - snprintf(owner, owner_size, "uid:%d", st.st_uid); - } - return 0; - } - return -1; -} - void scan_folder(const char *base, ScannerContext *ctx) { PathBuilder pb; path_builder_init(&pb, base); @@ -631,6 +746,14 @@ void scan_folder(const char *base, ScannerContext *ctx) { if (dir_fd == -1) return; +#if CHECK_FILE_SYSTEM + struct statfs fs; + FileSystemType fs_type = FS_UNKNOWN; + if (fstatfs(dir_fd, &fs) == 0) { + fs_type = fs_from_magic(fs.f_type); + } +#endif + DIR *dir = fdopendir(dir_fd); if (!dir) { close(dir_fd); @@ -649,6 +772,7 @@ void scan_folder(const char *base, ScannerContext *ctx) { path_builder_set_filename(&pb, entry->d_name, name_len); int file_type = DT_UNKNOWN; + #ifdef _DIRENT_HAVE_D_TYPE file_type = entry->d_type; #endif @@ -671,11 +795,16 @@ void scan_folder(const char *base, ScannerContext *ctx) { // Use fstatat for file info struct stat st; if (fstatat(dir_fd, entry->d_name, &st, 0) == 0) { - // Convert times using fd variant +#if FILE_TIMES platform_get_file_times_fd(dir_fd, entry->d_name, &fe->created_time, &fe->modified_time); +#endif + +#if FILE_OWNER platform_get_file_owner_fd(dir_fd, entry->d_name, fe->owner, sizeof(fe->owner)); +#endif + fe->size_bytes = (uint64_t)st.st_size; // Normalize path @@ -687,6 +816,10 @@ void scan_folder(const char *base, ScannerContext *ctx) { fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false); strcpy(fe->path, temp_path); +#if CHECK_FILE_SYSTEM + fe->fs_type = fs_type; +#endif + mpmc_push(ctx->file_queue, fe); } continue; @@ -706,9 +839,15 @@ void scan_folder(const char *base, ScannerContext *ctx) { atomic_fetch_add(&g_files_found, 1); FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true); +#if FILE_TIMES platform_get_file_times(pb.buffer, &fe->created_time, &fe->modified_time); +#endif + +#if FILE_OWNER platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner)); +#endif + fe->size_bytes = (uint64_t)st.st_size; char temp_path[MAX_PATHLEN]; @@ -719,12 +858,16 @@ void scan_folder(const char *base, ScannerContext *ctx) { fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false); strcpy(fe->path, temp_path); +#if CHECK_FILE_SYSTEM + fe->fs_type = fs_type; +#endif + mpmc_push(ctx->file_queue, fe); } } } - closedir(dir); // Closes dir_fd automatically + closedir(dir); } #endif @@ -786,17 +929,29 @@ static THREAD_RETURN hash_worker(void *arg) { char hash[HASH_STRLEN]; xxh3_hash_file_stream(fe->path, hash, buf); + double size_kib = (double)fe->size_bytes / 1024.0; + char stack_buf[KiB(4)]; + int len; + +#if FILE_TIMES char created[32], modified[32]; format_time(fe->created_time, created, sizeof(created)); format_time(fe->modified_time, modified, sizeof(modified)); +#endif - double size_kib = (double)fe->size_bytes / 1024.0; - - char stack_buf[1024]; - - 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); +#if FILE_TIMES && FILE_OWNER + 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); +#elif FILE_TIMES + len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\t%s\t%s\n", hash, + fe->path, size_kib, created, modified); +#elif FILE_OWNER + len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\t%s\n", hash, + fe->path, size_kib, fe->owner); +#else + len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\n", hash, + fe->path, size_kib); +#endif char *dst = arena_push(&ctx->arena, len, false); memcpy(dst, stack_buf, len); @@ -880,7 +1035,7 @@ static THREAD_RETURN progress_thread(void *arg) { return THREAD_RETURN_VALUE; } -// ======================== IO Ring implemented ======================== +// ======================== IO Ring implementation ======================== #if USE_IORING // -------------------------- Data structures --------------------------- @@ -914,16 +1069,16 @@ typedef struct iovec IORING_BUFFER_INFO; typedef struct FileReadContext { FileEntry *fe; - uint64_t file_size; + size_t file_size; // For in-order hashing - uint64_t next_read_offset; + size_t next_read_offset; IoBuffer *head; IoBuffer *tail; // Completion tracking - uint64_t bytes_hashed; + size_t bytes_hashed; uint32_t reads_hashed; uint32_t reads_submitted; @@ -953,22 +1108,21 @@ typedef struct IoBuffer { FileReadContext *file; void *data; size_t size; - uint64_t offset; + size_t offset; size_t bytes_read; BUILD_READ_RETURN_VALUE result; - int completed; - struct IoBuffer *next; int buffer_id; + struct IoBuffer *next; } IoBuffer; // Thread-local I/O Ring context +#if defined(_WIN32) || defined(_WIN64) + typedef struct ThreadIoContext { IoRingHandle ring; -#if defined(_WIN32) || defined(_WIN64) void *completion_event; -#endif void *fallback_buffer; IoBuffer buffers[NUM_BUFFERS_PER_THREAD]; @@ -978,10 +1132,6 @@ typedef struct ThreadIoContext { int active_files; bool submitting; -#if defined(__linux__) - bool use_registered_buffers; -#endif - #if USE_REGISTERED_FILES bool use_registered_files; FileHandle registered_handles[MAX_ACTIVE_FILES]; @@ -989,6 +1139,24 @@ typedef struct ThreadIoContext { } ThreadIoContext; +#elif defined(__linux__) +typedef struct ThreadIoContext { + IoRingHandle ring; + + void *fallback_buffer; + IoBuffer buffers[NUM_BUFFERS_PER_THREAD]; + int buffer_pool[NUM_BUFFERS_PER_THREAD]; + int free_count; + int num_submissions; + int active_files; + bool submitting; + + bool use_registered_buffers; + bool use_registered_files; + +} ThreadIoContext; + +#endif typedef struct { uint32_t MaxSubmissionQueueSize; uint32_t MaxCompletionQueueSize; @@ -1013,34 +1181,26 @@ static void ioring_query_capabilities(IoRingCapabilities *caps) { caps->MaxVersion = win_caps.MaxVersion; } -static void *ioring_create_completion_event(void) { - return CreateEvent(NULL, FALSE, FALSE, NULL); -} - -static void ioring_set_completion_event(IoRingHandle ring, void *event) { - SetIoRingCompletionEvent(ring, event); -} - -static void ioring_wait_for_completion(ThreadIoContext *ctx) { - if (ctx->num_submissions > 0) { - WaitForSingleObject(ctx->completion_event, SUBMIT_TIMEOUT_MS); - return; - } -} - static int create_ioring(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 = ioring_create_completion_event(); - if (thread_ctx->completion_event) { - ioring_set_completion_event(thread_ctx->ring, thread_ctx->completion_event); - } + thread_ctx->completion_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (thread_ctx->completion_event) + SetIoRingCompletionEvent(thread_ctx->ring, thread_ctx->completion_event); return SUCCEEDED(hr) ? 0 : -1; } +static int close_ioring(ThreadIoContext *thread_ctx) { + + if (thread_ctx->completion_event) + CloseHandle(thread_ctx->completion_event); + CloseIoRing(thread_ctx->ring); + return 0; +} + #define USERDATA_REGISTER 1 #define MAKE_BUF_INFO(a, l) \ @@ -1053,7 +1213,9 @@ static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, // The wait_count in windows is not implemented yet, so we wait with a // completion event for a single completion - ioring_wait_for_completion(thread_ctx); + if (thread_ctx->num_submissions > 0) { + WaitForSingleObject(thread_ctx->completion_event, SUBMIT_TIMEOUT_MS); + } return SUCCEEDED(hr) ? 0 : -1; } @@ -1071,8 +1233,8 @@ static void ioring_register_buffers(ThreadIoContext *thread_ctx, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), error_msg, sizeof(error_msg), NULL); - fprintf(stderr, "Error registering buffers: %s (0x%08X)\n", error_msg, - (unsigned int)hr); + fprintf(stderr, "WARNING: Error registering buffers: %s (0x%08X)\n", + error_msg, (unsigned int)hr); } // Submit registration ioring_submit(thread_ctx, 0, 0, NULL); @@ -1098,17 +1260,13 @@ static void ioring_register_files(ThreadIoContext *thread_ctx) { thread_ctx->use_registered_files = (hr == 0); } -#endif -static void ioring_close_event(void *event) { CloseHandle(event); } - -static int close_ioring(ThreadIoContext *thread_ctx) { - - if (thread_ctx->completion_event) - ioring_close_event(thread_ctx->completion_event); - CloseIoRing(thread_ctx->ring); - return 0; +static void ioring_register_files_update(ThreadIoContext *thread_ctx, + FileReadContext *file) { + thread_ctx->registered_handles[file->slot_id] = file->file_handle; + ioring_register_files(thread_ctx); } +#endif static BUILD_READ_RETURN_VALUE ioring_build_read(ThreadIoContext *thread_ctx, FileReadContext *file_ctx, @@ -1143,7 +1301,8 @@ static BUILD_READ_RETURN_VALUE ioring_build_read(ThreadIoContext *thread_ctx, error_msg, sizeof(error_msg), NULL); fprintf(stderr, - "ERROR: Building read error for file: %s - Code: %s (0x%08X)\n", + "ERROR: Building read error for file '%s' - Error: %s (Code: " + "0x%08X)\n", file_ctx->fe->path, error_msg, (unsigned int)hr); } return hr; @@ -1156,15 +1315,17 @@ static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { HRESULT hr = PopIoRingCompletion(ring, &win_cqe); if (hr == S_FALSE) + // No CQE available 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) + // Unlike linux, The Windows implementation treats buffer and file + // registration as an asynchronous operation that we submit to the ring, + // similar to a read or write. Those operations produce CQEs (completion + // queue entries) that we filter here using + // cqe.UserData == USERDATA_REGISTER if (win_cqe.UserData == USERDATA_REGISTER) continue; @@ -1187,15 +1348,24 @@ static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { } fprintf(stderr, - "WARNING: I/O completion error for file '%s' - Code: 0x%lx, " - "Error: %s\n", - file_path, win_cqe.ResultCode, error_msg); + "WARNING: I/O completion error for file '%s' - Error: %s (Code: " + "0x%lx)\n", + file_path, error_msg, win_cqe.ResultCode); } return 1; } } +FileHandle ioring_open_file(FileEntry *fe) { + + FileHandle handle = os_file_open(fe->path, FLAG_ASYNC_DIRECT_READ); + if (handle == INVALID_FILE_HANDLE) { + return os_file_open(fe->path, FLAG_SEQUENTIAL_READ); + } + return handle; +} + #elif defined(__linux__) // Linux io_uring functions implementation static void ioring_query_capabilities(IoRingCapabilities *caps) { @@ -1212,8 +1382,8 @@ static void ioring_query_capabilities(IoRingCapabilities *caps) { // static int async_io_create_ring(uint32_t queue_size, AsyncIoRing *ring) { static int create_ioring(ThreadIoContext *thread_ctx, uint32_t queue_size) { - IoUring *impl = (IoUring *)calloc(1, sizeof(IoUring)); - if (!impl) + IoUring *ring_impl = (IoUring *)calloc(1, sizeof(IoUring)); + if (!ring_impl) return -1; // Initialize io_uring @@ -1224,27 +1394,41 @@ static int create_ioring(ThreadIoContext *thread_ctx, uint32_t queue_size) { 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 + // and groupe them according 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); + int ret = io_uring_queue_init_params(queue_size, &ring_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); + ret = io_uring_queue_init(queue_size, &ring_impl->ring, 0); if (ret < 0) { - free(impl); + free(ring_impl); return -1; } } - impl->cqe_cache = NULL; - impl->cqe_cache_index = 0; - impl->cqe_cache_count = 0; + ring_impl->cqe_cache = NULL; + ring_impl->cqe_cache_index = 0; + ring_impl->cqe_cache_count = 0; + + thread_ctx->ring = ring_impl; + return 0; +} + +static int close_ioring(ThreadIoContext *thread_ctx) { + IoUring *rimg_impl = (IoUring *)thread_ctx->ring; + if (!rimg_impl) + return -1; + + if (thread_ctx->use_registered_buffers) { + io_uring_unregister_buffers(&rimg_impl->ring); + } + io_uring_queue_exit(&rimg_impl->ring); + free(rimg_impl); - thread_ctx->ring = impl; return 0; } @@ -1254,132 +1438,79 @@ static int create_ioring(ThreadIoContext *thread_ctx, uint32_t queue_size) { static void ioring_register_buffers(ThreadIoContext *thread_ctx, uint32_t num_buffers, IORING_BUFFER_INFO *buf_info) { - IoUring *impl = (IoUring *)thread_ctx->ring; - int hr = io_uring_register_buffers(&impl->ring, buf_info, num_buffers); + int ret = io_uring_register_buffers(&((IoUring *)thread_ctx->ring)->ring, + buf_info, num_buffers); - if (hr < 0) { - if (hr == -ENOMEM) { + if (ret < 0) { + if (ret == -ENOMEM) { struct rlimit limit; getrlimit(RLIMIT_MEMLOCK, &limit); fprintf(stderr, "WARNING: Buffer registration failed due to memlock limits " "(ENOMEM).\n" - "Increase the limit to solve this warning.\n"); - - // TODO: document this in read me - // The memlock limit in Linux restricts the amount of memory a process can - // "lock" into physical RAM using the mlock() family of system calls. This - // prevents the operating system from swapping that memory out to disk. - // And registering buffers will lock the buffers memory so the hardware - // can access it directly without kernel intervention. Increase the limit - // to be able to register the buffers. - // - // **Modifying the Limit: - // The method for changing the memlock limit depends on whether you are - // managing a user session or a system service. - // 1. For Users and Interactive Sessions - // To permanently increase the limit for a specific user or group, modify - // the /etc/security/limits.conf file. Add the following lines: - // # Example for a specific user (replace 'username') - // username soft memlock unlimited - // username hard memlock unlimited - // - // # Example for all users - // * soft memlock unlimited - // * hard memlock unlimited - // - // Soft Limit: The value the user starts with; can be raised up to the - // hard limit. - // - // Hard Limit: The absolute maximum; only a privileged user - // (root) can increase this. Values: Can be set in Kilobytes (KB) or as - // unlimited. - // - // 2. For Systemd Services - // Settings in limits.conf do not affect background services managed by - // systemd. To increase the limit for a service, edit its service file - // (e.g., /etc/systemd/system/myservice.service) and add: - // - // [Service] - // LimitMEMLOCK=infinity + "See README for more informations.\n"); } else { // For any other error (e.g., EFAULT, EBUSY, EINVAL) - fprintf(stderr, "Error registering buffers: %s (code: %d)\n", - strerror(-hr), hr); + fprintf(stderr, "WARNING: Error registering buffers: %s (code: %d)\n", + strerror(-ret), ret); } fprintf(stderr, "Falling back to unregistered buffers (performance may " "be reduced).\n"); } - thread_ctx->use_registered_buffers = (hr == 0); + thread_ctx->use_registered_buffers = (ret == 0); } +#if USE_REGISTERED_FILES static void ioring_register_files(ThreadIoContext *thread_ctx) { - IoUring *impl = (IoUring *)thread_ctx->ring; - int hr = io_uring_register_files(&impl->ring, thread_ctx->registered_handles, - MAX_ACTIVE_FILES); - if (hr < 0) { - fprintf(stderr, "file registeration failed: %s (code: %d)\n", strerror(-hr), - hr); + int ret = io_uring_register_files_sparse(&((IoUring *)thread_ctx->ring)->ring, + MAX_ACTIVE_FILES); + if (ret < 0) { + fprintf(stderr, + "WARNING: File registeration failed, fallback to unregistered " + "files - Error: %s (code: %d)\n", + strerror(-ret), ret); } - thread_ctx->use_registered_files = (hr == 0); + thread_ctx->use_registered_files = (ret == 0); } -static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, - uint32_t timeout_ms, uint32_t *submitted) { - IoUring *impl = (IoUring *)thread_ctx->ring; - if (!impl) - return -1; +static void ioring_register_files_update(ThreadIoContext *thread_ctx, + FileReadContext *file) { - int ret; - - if (wait_count > 0) { - ret = io_uring_submit_and_wait(&impl->ring, wait_count); - } else { - ret = io_uring_submit(&impl->ring); - } + // Update the kernel's file table at the specific slot + int ret = io_uring_register_files_update( + &((IoUring *)thread_ctx->ring)->ring, + file->slot_id, // offset - which slot to update + &file->file_handle, // pointer to the fd + 1 // number of files to update + ); if (ret < 0) { - fprintf(stderr, "submit error: %s\n", strerror(-ret)); - return -1; + fprintf(stderr, + "WARNING: File registration update failed for slot %u updating " + "file '%s' - Error: %s " + "(code: %d)\n" + "Fallback to unregistered files\n", + file->slot_id, file->fe->path, strerror(-ret), ret); + + thread_ctx->use_registered_files = false; } - - if (submitted) - *submitted = (uint32_t)ret; - - return 0; -} - -static int close_ioring(ThreadIoContext *thread_ctx) { - IoUring *impl = (IoUring *)thread_ctx->ring; - if (!impl) - return -1; - - if (thread_ctx->use_registered_buffers) { - io_uring_unregister_buffers(&impl->ring); - } - io_uring_queue_exit(&impl->ring); - free(impl); - - return 0; } +#endif static int ioring_build_read(ThreadIoContext *thread_ctx, FileReadContext *file_ctx, uint32_t buffer_id, size_t size, uint64_t offset, uintptr_t user_data) { - IoRingHandle ring = thread_ctx->ring; - IoUring *impl = (IoUring *)ring; - if (!impl) - return -1; - struct io_uring_sqe *sqe = io_uring_get_sqe(&impl->ring); + struct io_uring_sqe *sqe = + io_uring_get_sqe(&((IoUring *)thread_ctx->ring)->ring); if (!sqe) { printf("SQE FULL\n"); return -1; @@ -1387,6 +1518,23 @@ static int ioring_build_read(ThreadIoContext *thread_ctx, void *buf = thread_ctx->buffers[buffer_id].data; +#if USE_REGISTERED_FILES + if (thread_ctx->use_registered_files) { + sqe->flags |= IOSQE_FIXED_FILE; + + if (thread_ctx->use_registered_buffers) { + io_uring_prep_read_fixed(sqe, file_ctx->slot_id, buf, size, offset, + buffer_id); + } else { + io_uring_prep_read(sqe, file_ctx->slot_id, buf, size, offset); + } + + io_uring_sqe_set_data64(sqe, user_data); + return 0; + } +#endif + + // Fallback: use regular file descriptor if (thread_ctx->use_registered_buffers) { io_uring_prep_read_fixed(sqe, file_ctx->file_handle, buf, size, offset, buffer_id); @@ -1398,12 +1546,34 @@ static int ioring_build_read(ThreadIoContext *thread_ctx, return 0; } +static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count, + uint32_t timeout_ms, uint32_t *submitted) { + int ret; + + if (wait_count > 0) { + ret = io_uring_submit_and_wait(&((IoUring *)thread_ctx->ring)->ring, + wait_count); + } else { + ret = io_uring_submit(&((IoUring *)thread_ctx->ring)->ring); + } + + if (ret < 0) { + fprintf(stderr, "ERROR: Submit error: %s (Code: %d)\n", strerror(-ret), + ret); + return -1; + } + + if (submitted) + *submitted = (uint32_t)ret; + + return 0; +} + static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { - IoUring *impl = (IoUring *)ring; struct io_uring_cqe *cqe_ptr = NULL; - int ret = io_uring_peek_cqe(&impl->ring, &cqe_ptr); + int ret = io_uring_peek_cqe(&((IoUring *)ring)->ring, &cqe_ptr); if (ret == -EAGAIN) { // No CQE available @@ -1412,7 +1582,8 @@ static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { if (ret < 0) { // Error - fprintf(stderr, "io_uring_peek_cqe error: %d (%s)\n", ret, strerror(-ret)); + fprintf(stderr, "WARNING: io_uring_peek_cqe error - Error: %s (Code: %d)\n", + strerror(-ret), ret); return -1; } @@ -1432,7 +1603,7 @@ static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { cqe->UserData = (uintptr_t)cqe_ptr->user_data; - io_uring_cqe_seen(&impl->ring, cqe_ptr); + io_uring_cqe_seen(&((IoUring *)ring)->ring, cqe_ptr); // Check for error and print warning if (res < 0) { @@ -1445,13 +1616,28 @@ static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) { fprintf( stderr, - "WARNING: I/O completion error for file '%s' - Code: %d, Error: %s\n", - file_path, res, strerror(-res)); + "WARNING: I/O completion error for file '%s' - Error: %s (Code: %d)\n", + file_path, strerror(-res), ret); } return 1; } +FileHandle ioring_open_file(FileEntry *fe) { + +#if CHECK_FILE_SYSTEM + if (!fs_policy(fe->fs_type)) { + return os_file_open(fe->path, FLAG_SEQUENTIAL_READ); + } +#endif + + FileHandle handle = os_file_open(fe->path, FLAG_ASYNC_DIRECT_READ); + if (handle == INVALID_FILE_HANDLE) { + return os_file_open(fe->path, FLAG_SEQUENTIAL_READ); + } + return handle; +} + #endif // OS-agnostic helper macros @@ -1606,7 +1792,6 @@ static IoBuffer *get_free_buffer(ThreadIoContext *ctx) { int idx = ctx->buffer_pool[--ctx->free_count]; IoBuffer *buf = &ctx->buffers[idx]; - buf->completed = 0; buf->bytes_read = 0; buf->result = IO_PENDING; buf->next = NULL; @@ -1621,28 +1806,6 @@ static void return_buffer(ThreadIoContext *ctx, IoBuffer *buf) { ctx->buffer_pool[ctx->free_count++] = buf->buffer_id; } -// -------------------------- Build read --------------------------- -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; - - BUILD_READ_RETURN_VALUE result = ioring_build_read( - thread_ctx, file_ctx, buf->buffer_id, size, offset, (uintptr_t)buf); - - if (IORING_SUCCEEDED(result)) { - file_ctx->active_reads++; - file_ctx->reads_submitted++; - thread_ctx->num_submissions++; - } else { - buf->completed = true; - buf->result = result; // Store the error code - return_buffer(thread_ctx, buf); - } - return result; -} - // -------------------------- Process completions --------------------------- static void process_completions(ThreadIoContext *thread_ctx, FileQueue *fq) { IoRingCQE cqe; @@ -1664,26 +1827,31 @@ static void process_completions(ThreadIoContext *thread_ctx, FileQueue *fq) { // -------------------- File operations ----------------------- static int init_file(ThreadIoContext *thread_ctx, FileReadContext *file, FileEntry *fe) { + +#if USE_REGISTERED_FILES + uint32_t saved_slot_id = file->slot_id; +#endif + memset(file, 0, sizeof(*file)); file->fe = fe; file->file_size = fe->size_bytes; - file->head = file->tail = NULL; - file->file_handle = os_file_open(fe->path, FLAG_ASYNC_DIRECT_READ); + file->file_handle = ioring_open_file(fe); if (file->file_handle == INVALID_FILE_HANDLE) { #if IORING_DEBUG_PRINTS - printf("ERROR: Could not open file %s\n", fe->path); + printf("ERROR: Could not open file '%s'\n", fe->path); #endif return 0; } -#if (defined(_WIN32) || defined(_WIN64)) && USE_REGISTERED_FILES +#if USE_REGISTERED_FILES + file->slot_id = saved_slot_id; + if (thread_ctx->use_registered_files) { - thread_ctx->registered_handles[file->slot_id] = file->file_handle; - ioring_register_files(thread_ctx); + ioring_register_files_update(thread_ctx, file); } #endif @@ -1691,8 +1859,6 @@ static int init_file(ThreadIoContext *thread_ctx, FileReadContext *file, if (file->file_size > g_ioring_buffer_size) { file->use_incremental_hash = true; XXH3_128bits_reset(&file->hash_state); - } else { - file->use_incremental_hash = false; } return 1; } @@ -1727,15 +1893,29 @@ static void finalize_file(ThreadIoContext *thread_ctx, xxh3_hash_file_stream(fe->path, hash, thread_ctx->fallback_buffer); } + double size_kib = (double)fe->size_bytes / 1024.0; + char stack_buf[KiB(4)]; + int len; + +#if FILE_TIMES char created[32], modified[32]; format_time(fe->created_time, created, sizeof(created)); format_time(fe->modified_time, modified, sizeof(modified)); +#endif - double size_kib = (double)fe->size_bytes / 1024.0; - - 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); +#if FILE_TIMES && FILE_OWNER + 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); +#elif FILE_TIMES + len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\t%s\t%s\n", hash, + fe->path, size_kib, created, modified); +#elif FILE_OWNER + len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\t%s\n", hash, + fe->path, size_kib, fe->owner); +#else + len = snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\n", hash, fe->path, + size_kib); +#endif char *dst = arena_push(&worker_ctx->arena, len, false); memcpy(dst, stack_buf, len); @@ -1929,10 +2109,10 @@ static THREAD_RETURN hash_worker_ioring(void *arg) { process_completions(thread_ctx, &fq); #if IORING_DEBUG_STATS - printf("Free buffers: %d, Submissions: %d, Active files: %d, fq count: - % d\n ", thread_ctx->free_count, thread_ctx->num_submissions, - thread_ctx->active_files, - fq.count); + printf( + "Free buffers: %d, Submissions: %d, Active files: %d, fq count: %d\n", + thread_ctx->free_count, thread_ctx->num_submissions, + thread_ctx->active_files, fq.count); #endif // Hash files