From e117334deefcb92f650d48d63fa107b4af09aa0d Mon Sep 17 00:00:00 2001 From: amir Date: Wed, 15 Apr 2026 21:03:54 +0100 Subject: [PATCH] Porting IO Ring to linux by implementing io_uring --- .gitignore | 2 + 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 | 997 ++++++++++++++++++++++++++++++----------- 9 files changed, 1225 insertions(+), 289 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/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..563cd6f 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 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, it supports bashing multiple submissions and can handle multiple files at the same time. Hashing small files using XXH3_128bits() instead of the streaming pipeline(XXH3_128bits_reset(), XXH3_128bits_update(), XXH3_128bits_digest()), this reduses the overhead of creating a state and digest, coupled with the IO Ring it improves the hashing of small files whose size is inferior to the size of IO Ring buffers fixing the xxh_x86dispatch warnings Updating the progress printing function diff --git a/compile_commands.json b/compile_commands.json index 34a424d..0b29205 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -4,4 +4,4 @@ "command": "clang-cl /O2 file_hasher.c xxh_x86dispatch.c", "file": "file_hasher.c" } -] \ No newline at end of file +] diff --git a/file_hasher b/file_hasher new file mode 100644 index 0000000000000000000000000000000000000000..8c12d074d16ec49d546cfa63db36d9f337926f66 GIT binary patch literal 245536 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 literal 0 HcmV?d00001 diff --git a/file_hasher.c b/file_hasher.c index 9bbe3d2..7948f12 100644 --- a/file_hasher.c +++ b/file_hasher.c @@ -87,7 +87,7 @@ int main(int argc, char **argv) { printf(" Selected instruction set: %s\n", get_xxhash_instruction_set()); // Align IO Ring block size to the system page size - g_ioring_buffer_size = ALIGN_UP_POW2(IORING_BUFFER_SIZE, g_pagesize); + g_ioring_buffer_size = ALIGN_UP_POW2(g_ioring_buffer_size, g_pagesize); // ------------------------------- // Scanning and hashing // ------------------------------- @@ -253,7 +253,7 @@ int main(int argc, char **argv) { FILE *f = fopen(FILE_HASHES_TXT, "wb"); - for (int i = 0; i < num_threads; i++) { + for (int i = 0; i < num_hash_threads; i++) { mem_arena *arena = workers[i].arena; u8 *arena_base = (u8 *)arena + ALIGN_UP_POW2(sizeof(mem_arena), arena->align); @@ -265,14 +265,13 @@ int main(int argc, char **argv) { // ------------------------------- // Print summary // ------------------------------- - // DEBUG uint64_t incomplete = atomic_load(&g_io_ring_fallbacks); if (incomplete > 0) { - printf( - "\nI/O Ring incomplete files: %llu (fallback to buffered I/O used)\n", - (unsigned long long)incomplete); + printf("\nWARNING: I/O Ring incomplete files: %llu (fallback to buffered " + "I/O used)\n", + (unsigned long long)incomplete); } - // + double total_seconds = timer_elapsed(&total_timer); printf("Completed hashing %zu files\n", total_found); diff --git a/io_uring_test b/io_uring_test new file mode 100644 index 0000000000000000000000000000000000000000..6315aaab05dd61e1fb713218a40b7939f688c2a6 GIT binary patch literal 27808 zcmeHwe|%KcnfFZ~h*V7cfu*8wQKO&|!%z7km;eJ41&JiFpwMB+Ovuz^Ce9B{tg+D; zUv9_wY;=pozS`1$ww7&oUDj3?Ska)CpiA5M<7H!uYf7m*BiN>0T4QC+`+c7CoI7{M zT({0-cGRAK>7mnOt(2GFZ5j>4g-6whsWA#m!3mABLxZ(n!)CsS^n za(i3v`WXR^||HK79>4I-Y!6W$@feY9{!Hs2>ODpPHlyS!oYk! zySy#IxUc@Mpb-nVp!|4)5eqi^0>M~38fi0Hg3)Ls$`ISd+G6#Qa0BdPaew`tzWT;H zeGUFllhF_j291_PyuQ&NHJTIQW`B#(5NQd9jYO;|7;G`%5C5~Lc(m;vKL>${vz|Of zRbe*N zH$`GF4>rewaaM1M#NtK+;))?;D1{?Fc=E?Xk+9M158NFhb&eHL#0|v5ouKA!ijL0a z8LO(6)~&6&WudrmK?TM&n+nkbj6*7~LI>0Y(y~r)4BL3+NbI zLf5AP<6-1DT`x`iwD>MNo9?i_1&odYoPS;9#6SM% zC$eAoAG3Uv@h`$BE)Kzep>Y^I9r`+~??+lLQ20T)Z_>Cac=@eLu5r!xtWh}KBj{T( zLJm4`=lw&U1HVX_^6!2Jez^l5aNt)taHCo=XMsqS7#Qoo zYokvbIF+G~X%1Ys1R>m0b;y0dJP z1249<4WrS4mpJei2Y#LdZ*}0#dAGxXONy}7RtHY^ar)?X;1e_my4`_)-huCM;1eDA zP6z%42foXJU+lnlJMc*kyvKp-XCx9Ibl`MvqmMoZE{{4a>UZE%v@+;`12-Lb+JR4X z;DZj_dEFaw;8!^G4H=J=o23qXtOLK&ftNV&X%2j%1E21|O$UCp13z<`+XDYlJo(Sw z&J$z3$)f#ul=F7=#0!V}yq&L(eT5Z==e`bjxbzTy7f-3c8S#xI%M1+TD1DiDn!+-D z%dG@ie7mHZfmFJWVB;YUcmE1UyY4nF{9rKs-$ynR4bo zAfBd-jLG~_;%TbLlraAb;wKYtF#jg;Q-~k@Ppbdx#GAwqF#j^~G!RIRnhG*Iz*GLs{IJS;<4a!asJHXjV0Eo$X3xymys5?aq0++>CM`CM zlMQ9XQyzi|5;V2=0XXocitZ({U?@HTsr%^-Ds|5u9w?sDL3*zUh5oIqKldgg(?9iE zgWmnWz0tdWXq30$fcK40;}^ogPsPF5;em$YDa*6=6ko^U@vt=#v+KN_i+=!3!)yH} zKHi&L{96P!JYhSabUoVUK+!P(1sh+<#)~*AjD{>v{xVyjTL&Lj`>CYoRH=b%-k<(l z=RiU6^F141Aqfvjt9{6u^b93CdQha*n|=*uuPLz>Rh(05bQlIk?OD5N%pEVn*=0dI12Db1T+%QG~yr?dTSBR&((`@Nk#jlj}4#}A1Iy7~%v`V@`h63Jel&g4AI(*8GsPjY#${O-oi9v5?`=AlOglxPB`(}h~y-cRoVdDMZ zyB+l)3kbc*I;dBh-4eW15kchw2Pz5-(r@UkK#l3Fx8f4iOoOPUh%xUg@wHm)?X4(x zoX@9oDGz-?v0HVh!s_&LRC|@Rx*y%cp$~2?fvdq}-H=s1n$8eyKcdCFSx0S0k&DJL zt9qiYL+6QU)F@Wsq}QrTTdN0GS)PIA*6JhPWcxsp>b*LhtUF0jR#_icA~mf8FO5b} z=@1$z={cg>t--3ErdzD@L^<_oUWdX%zsi%&fkI@%7YNAq=U~#XJSUQ#6UWaM0lFHM zL0?|TX@S1{?w@sE>a;{4`_tq8PtvmUgsGFjsymUaJK^O@dWI@@Q&U@S>?J};F!~c+ z8dBXhqEItR?Io`e@12EajBmC}7OCp!5!zMk}`hMhyhNPfmV%64p5Puux? zO8w(WTP4RwcONh4W($TjCOro&&)XuRk%u~lL(;ii<{p%Z{<{D;WPD#Coix!_rn`^kskBfV^4FW5g~F`;m2Vwe zZVhnTjqw&>Fe3pxM^e=VWMfq?2T7B52Qrn|Q!-B2FhLzhTtu>&s=p04-^C1HL>M+^-oq$;CfIAo`!a%Ck@ zIqu^0Ub?fAMjV8_ae#$@^<63eWGb&Kb_GHcUKq|G}(_|!i4`lV+-6NB@-FT+I z&eeh&kETT$-gPHmRN?fd$HRgLp)-0`?j}`g>b-wNHluycNq6HcX><;aj{iD4JLYuD zi_rLtJ8nZOaje&Kii$m6q9X21m;VW#J*NsV=`Zpei>A9>yiP=r4w0HJLp^D91>(Cn zSIUEgLQhTDj=Mfg@Lua*dAdrsAUcPi>~O+`kS)ePtSx@7ES8L5p?u%3HO2RZuA0qx znjf6ZjUrVo!8Jri#Xk)?8>&FJ;FI=8p6(xRQKtTYygcs2|OL2gmm^ zia5&E;6!`^vk~kuNsP7!_-GaD<V0<9 zeYpAHs56wgy`DkN-xlR^EZTejz&YOfK5F>wd`UpE(S1O{pts<4la1J8cPS0QgZtq6j|20ezG!>`!I;My)G%U}_&i0cA^~g!?k=Oo1)jw7A zH+T$^lsL<^ZpW!dwy|b!`gwI}?n_FK^jSGQ^1z86G=bOJ=cNR(o}(weK<=$BoYB)4 z%^bfYXQ{4Dq??-XB|M|4(U|@s!p5D&^7NA5OMgtTW1gRlxrT%3qC#|+Fk|=1vy_z| zeT+8fs-;4bo^G$zwT@2}Nlp0BE>;ccJE=&eo|=$`&@n-*$;#?+HDniR2z>N=ul0u4 zI!Mp!HJ$sYG$UTB{B))ZkOZRcEqGa>AN3ipas{PtSgrV*2(w+XAPE|W=WBFJYm?=aesGA}^^q_$D!CA*ZD z!+>8xOLN{G_^nj_DQbJJ{L5_0mH#C&3YDiK?7Sn!&bxlfyRXv4mg^;XR}B?!FZU7+ zW9cyOf-VxoHg)c!)->YRsAfghpxh)2IcZ3W@!Nx>@mlQV__P>c)lS`TF zD0=D|n$_^|${mzceWr#^rS+OThkQ&?-HuU@bfyl3B?qT2aho|y?dbP>NDaFjYMuM& zx6!sV$t=%q$Em8^V+m?x=DI*bNQQ>h(GFzUto} zWzDPH&8MQEZ?k4^dWh~tsFZ9IAz=GXhwmnEBWj_Fc-H{Ktzzhb|D@-bBeU@=LTQbb zr(4OQgedO&041b~x`cfOY_I1e#)xrzGPMm|2VhXpSNyzD{9+Hf?PjQSdQRdd_avI9 z{p<(YPxeCn{x=?IAE3wV`!9sQMarL=v|m$YTq9-NL=M^qj0Cmpr39vm`tZI~Qol^s z`Lo#*fx*Y5$#+NG5ARYJ<5q}#{X2X0sQt_+tx8j(Omf1dHXT(ybqpxJrB_{R&Q4;RB?i{`{XQhh#V)o$T1Cx zqkQS|?7#!u4mHEwh!@R>^h?<~oy)=UG^>-389l}_#f7*V<09_t2_OS3&raNG?NpE> zBh-wg=DK|pQBqYc>Df_feOzT7+{3dz!nzA7EasdKJEW`b#W?BYY87;Hfdxuyk>{b} zM@p$q*{c0GP1DELq-X0QwulFltG6b%QJZ*EbH9c6=iZcOSEY53ZcF~~Vg)sfzHitL zTau!WEmGv6nl7SSEOosZxgluD5!x=wCcWt&V!H12bilEZ`4PEK)pgqeaxw*Q;YN&| z?OSPz_H5<(`Q{9YvFLW9iXE}0#Z&)jI^kiWo>^whsorSb#&C6BodjJXcgdc}_{;f5<3FO~b5y5vwc%4DIEV&prNkKT7&15A2u zlsAktb+wMe&_=JXrBFKC(?)zEg5^i&Dbx}Cc;1_y4Idg`Nvf%$Rrtz6Y3zkaKbJmm zq8i{=R=U)DW?EAr`mb=$t)r%{>g zQchIINVKZihvCcWdI4p0?xWvE+i99(f$jFM*zLBQ*m$UZn{$&pFS(5zqSL5V?5EOy zz`f4tgMSOpZK@%rqZHlJx%nW?fsZV>**?9yhirGhpuR{IwczU`bxHZJDY~Nfgq^C` z?~=tmHm|%;F;t9$rdQU znkqUb-BG@1_N?YRjz0O<8_k-!Wy?G@H4Dws81QELn9lN%=nb46vjbfAF)Q>2u~hF`Q?D+ZYSuP}VkZ9gn{nux z4WXu>*$|1E{#aYM-mJQAjfqu)QC^W3D;u{e6b_oR7HYp{H0Te+%zHxdMx3fuk->l| z>mo&cHq;w~=3R+U{hj9KXyl$S3TQRIoM>)|nOII3HRBLB`R{Er1CcFaG-sh%xpdib z&x)1as+(?JwfdGd)wiy#sja*1i|f|kuELnR&@7)hYxbPE^RA!2V4QJ}K^3sqO_s%5 zgh8hjr1kg9l%D|TsSfv|qRL5BhPI6s< z$qQMVgW>o%CLr4uV=@>EUlli-{a8F~Y9Hp^ z{-#9Gq1GIVu|{4?a4`7g2oj5jsUG=lvmCLTm<>V=Az1*d>cK5bwJ@hOgs?{0oK;?K z(lX}Q^o6Lxj2SbGva&K`oH|ACu`W$ac1IZ=sW}=hj4Y3vlz9!2L^xnLip`H`StMMa zh(<}D+{JkIkP58~s{BKONN?uoW=kx^*w}*7N&`ksHf<3{j%&0}E|AS2RbX!BE$kk@t;=k}fAf~DFnlBe-O(GNzO7j_ip_|3yP;fIl zBZoL@JvFtsV6I+Wxput)HWK$YamU0ciO1;+E6sM4V+^tnuEES^OpiQcWd$4(OJ_lf z3$j_cY897LV^&wz)OeOpRZ$d78nvhcceK~RBG5lP{9~NI`itS=Cqei9a(MU%=)Z!V z0{s){CAidIa%6b;2GFgb8$e$Ky$|#q&?iAZ0DS?p>Rs4_J_ULTbP)6sJV(|38|*>D zpc_C_p!b2k4EiMK`uAWD+ByJx&^JI&f!_10;b9YBiXH{60R0Z=CeRb09iW%v=5hz< zgP=X2KL#BD{S_#dk{jpZ=G6qHd+Z9(0O%&ruYz`fJ_Wi1v=_7o^kdKg(94g(9;*pg zgPNc}2dx0T^Ea>u{ZBk|c7SfgYl0o1pWtD?2Xs5$jSPU&%U>*8H!j1w1ru}_&z%*Z zK0GgO0>#%lJ__z#YZSDW6ihnjtg+o_Z^CI+lKIZ?@Rg*o{4Bg-Reygs2XyCQ*rQ#I zb4ym7JMpIC&)qY&!?^L{MOV+h;xf`B+YR`62lf{eP%F1tm{7T4uPI;lK0F}Q1@*w23Ir24jeD^{A1mrb2@>^{AlaL>R zJdh*z+42`4FTuEQYu_V~kAvK;eNRDtHso`i{#&y3y9E6^8S-y9<%N&g<=+7LHu4WY zcK?mKxPTP6{*Zqi<8ghCeJ$IY!0v7_b`3}hcj8{nS=9J%Wm)`^VKjHQ4 zrkwKcu*)BS{3FOOLOW#RKW{WE03sN#?+mE%-}@GJaFfy{gAe?j6g9Cy;n7i;$CHf4 z9?9wNh3UX$$JS@y%&#*oaHa*$w7{7bIMV`WTHs6zoN0kGEpVm<&a}Xp7Wn_Z1@yib zdY=pZoJo6g(4l$Si-QiVw^0Yb@G%UmKjLFfnWB160=>TmzxRNM-Z|32?~y?DJ{$Cm zPluNO@zdc5@!fcDLI*t;)3I9~i0Rpuj$IH!RKynT-Gtz4m zEM|t@PmSKY(xIQfKd)G$RUB)4w-l`7?h!pKnBYT~|H-hb4?tG^RQUN~kM&MmUmXY5 zvob#*jy@J%$1U#vuM@qGTz7W%%>KM5U!KLw!UBB56bohNjK z(A$JI2)$dVvc|ifTh-ahWy=Yd{IpR8ap-yFFwP18Yr*;5JFtx4 z&kBAecsf4D59#*{&fhYC(SBouFP8~{zhMBUckP7VE4ZHDt`~fp;F31R8o{3yoZn-^ z&lbUpWP+*%Mn|jQ>jke-u<@wie#hl1ZP zIDd11Y*_FKG7<8(2H+Q9!lHJVBNH8eV*p+%cuMf6#s8Ip&z1{>j`MoKR|_t~-KY|L zqu~7g0KRS)e5c_2-2nKP1%FR)-OlulgW_Bw1jl4B{0s`-DY%aRQ^B9h(?1&*3aamW zdH98bpDh>M2gQG>;7P%s6nvrJS6!+Aet(Zs+831k?~%YaAr3lxf`4JE;`x0&PFn>3 zr^^-2@9A;cC3x)>3g`FpIDJa+M5)60y*y5z5&Yj|;PU%;;I9h)&(oFuCPj=xz(<-l z4g)X18}l1(6h#LaN>@Pq6n>)#@Jt$Bs6J-bYm9C=~ubZ{J^NoZkQEO{pxp$^~EK zg0FMI6E65x;HD13LHlrHWf~KWX)+26eH=s>4Ikn zFC1^^`$+BQcP{$lT;iGOf-iN!{VurA1&_Pn54+$!F7ZF%qW||U_|Je-JLmV;FM*r7 zu^cpj8`Y88{{!fsS2)qW578zc5sqD%_5Q>Nn~yQL;U3A)RlrMd-LJ^YpSR)Xe9R9W zVkE=WxK{M@^JKn@|8*|-os6GjOp*zbzpEkA78m`8T=4BK_%kl}-@D*1x!`ZQ;P1QO z#po~Um;Cmb1l&|zk@I`5OaI>Dg4es?ap0Hbcvj3d%L!kg70y` zjhgysd}dh$TT%KqhkSAW76ZU$tho)>0L|`O>5Dh}u+3&TNIPlevsqB^boMuFhqs+SITYKXX-SscK7;Kh8`G*8bAc zUxi=?R;)F~@=$E%$|?jPF=+Ezn-2u@k!Y|f=*Nz%tU}w+@}9FcM{#6jdKXz+%zM4s zoZi8fcc1k&#|+-x)+ftlg`>sAi6{!Boo`WELqw~^gDn^Uw5FRg4V{8*T5}eF`;=IL zpIKPVs}3ccSgZDK1s{N`XjU*0ypwiG+y=I^>|%gCGB?l%WI-7dM55!7V1 z0r?Noh%?mAzqWv!leXlDjgtY*J_rj7xgGl#+Zv7`o!_SwIlBPUuEDkym1hrF)y=uu zdUNF*3*MJF2T?m0=Ri~q%4{1%9qgjAyFBL>hj|yRqV^L;U2``V_61@QUnADJ)5gP9 zYoLXN@4f^Uv*tUd;g{zai3Nw7V=>#g9gJ)wook@Bg0_RFhT@%`_2{-GsFrNVY(uOp zTS4c5ys>l+qV}oIfn=-d9L$dOvzu(^hVAp!u3n~x4Q<@4lL2sc+vc3@sqKQWFSOo~ z+Qz6V{I>%(f-R$cKIb;pwuE%4d1MB&akP-yMB7G4Q*E!E0~uwpw&u8hGiW@jXrn&E z!t`L-mT;nMGyTECj8MQR!_*gywit{y`eThoS)eTp4~k;XZkAyBiP7JuI2j*gn6v$4 zAZIO2aq@_Xx-1^VU&xg4a4U;Oc$k(28|Cue7=W$jj50ecbT|4k-X>7x(yAGBFTyU> zjev5g&r!0!IaH6ZA}9>M$~Xd58q(A#ql*k~Ge)?l`tLyW7>`>hJaemqp1i6N=M+Hvk)w#CcCH9(* z6+=8WaGcs+zd!c^ClhV2zXNEh-}^&_kNo(nfzms6ZLi;}YkHg5>G-vtrW+xrclMgs z@9j115POQBbaeaz8}vSrd~19C-9XbGQPkyYdtLv##eS*i>F*FWmG&fidPSFS-vNx; z1dko+(D(B~W?1rkixd#3;|b@o=8KelFDXAM*mx_TtWXB+p*I zAJ9~Pf71H89X0=$*xO;2i$v%PG`JjxZa)ou6DL%EZLi<=XsZ9c7_FaQ{?0u6iUmqj z(`?5mJiq*B0XxV4Xa1cXWoG}*xBp3=z5YI+scBoPll=JK0*to;JO)Waf7jDAzdX%r z`W|eG9rpUWpQc)meAA(MO^*Yk+?6!9-^1zeQTpF8qVY$Ew$piZ5*YQLw%7ahYdVdL z9EY~o^kc}J_WHY_rWGV~9NJFPLdc!=`u&ZjI!^6E+u1yf3DfcG_XC>d>*s~T-!TZ& zX|Mfi`@G@Dv67DVLsaL!wk5hw-un#T6#*S|9aw@N9gm*B=)07T9jEp&e}g(blsEo# 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..50e27a4 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,376 @@ 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; + +#if defined(__linux__) + int use_registered_buffers; +#endif } 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 +1356,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 +1377,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 +1496,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 +1509,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 +1563,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 +1588,7 @@ static void process_completions(ThreadIoContext *ctx, FileQueue *fq) { } else { file->failed_reads++; - return_buffer(ctx, buf); + return_buffer(thread_ctx, buf); } } } @@ -1183,11 +1600,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 +1617,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 +1638,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 +1649,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 +1798,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 +1807,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 +1822,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 +1835,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 +1856,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 +1881,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; }