From f57214d9eefd1d9992cbbb543be8d9c32ebf2413 Mon Sep 17 00:00:00 2001 From: Oleg Stepanischev Date: Sun, 27 Jun 2021 16:21:38 +0300 Subject: [PATCH] 0.4: Ported zstd v1.5.0, performance improvements --- src/Zstd.Extern/ExternMethods.cs | 20 +- src/Zstd.Extern/libzstd.dll | Bin 614912 -> 669184 bytes src/ZstdSharp.Test/ZstdTest.cs | 8 +- .../{ => Polyfills}/BitOperations.cs | 20 +- .../SkipLocalsInitAttribute.cs | 4 +- src/ZstdSharp/Polyfills/Unsafe.cs | 77 + src/ZstdSharp/Polyfills/Vector128.cs | 54 + src/ZstdSharp/Polyfills/Vector128_1.cs | 36 + src/ZstdSharp/Unsafe/Arrays.cs | 52 +- src/ZstdSharp/Unsafe/Bitstream.cs | 12 - src/ZstdSharp/Unsafe/EntropyCommon.cs | 4 +- src/ZstdSharp/Unsafe/ErrorPrivate.cs | 1 - src/ZstdSharp/Unsafe/FSE_DecompressWksp.cs | 12 + src/ZstdSharp/Unsafe/Fse.cs | 2 - src/ZstdSharp/Unsafe/FseCompress.cs | 1 + src/ZstdSharp/Unsafe/FseDecompress.cs | 47 +- .../Unsafe/HUF_CompressWeightsWksp.cs | 15 + .../Unsafe/HUF_ReadDTableX1_Workspace.cs | 2 +- .../Unsafe/HUF_ReadDTableX2_Workspace.cs | 389 ++++ src/ZstdSharp/Unsafe/HUF_WriteCTableWksp.cs | 14 + .../Unsafe/HUF_buildCTable_wksp_tables.cs | 68 +- src/ZstdSharp/Unsafe/HUF_compress_tables_t.cs | 37 +- src/ZstdSharp/Unsafe/HufCompress.cs | 63 +- src/ZstdSharp/Unsafe/HufDecompress.cs | 103 +- src/ZstdSharp/Unsafe/Mem.cs | 2 +- src/ZstdSharp/Unsafe/Xxhash.cs | 5 - src/ZstdSharp/Unsafe/ZDICT_legacy_params_t.cs | 12 + src/ZstdSharp/Unsafe/ZDICT_params_t.cs | 9 +- src/ZstdSharp/Unsafe/ZSTD_BuildCTableWksp.cs | 11 + src/ZstdSharp/Unsafe/ZSTD_CCtx_params_s.cs | 9 + src/ZstdSharp/Unsafe/ZSTD_CCtx_s.cs | 3 + src/ZstdSharp/Unsafe/ZSTD_CDict_s.cs | 6 + src/ZstdSharp/Unsafe/ZSTD_DCtx_s.cs | 2 +- src/ZstdSharp/Unsafe/ZSTD_Vec256.cs | 12 + src/ZstdSharp/Unsafe/ZSTD_cParameter.cs | 3 + src/ZstdSharp/Unsafe/ZSTD_cwksp.cs | 8 +- src/ZstdSharp/Unsafe/ZSTD_entropyDTables_t.cs | 101 +- .../Unsafe/ZSTD_fseCTablesMetadata_t.cs | 4 +- .../Unsafe/ZSTD_hufCTablesMetadata_t.cs | 8 +- src/ZstdSharp/Unsafe/ZSTD_hufCTables_t.cs | 35 +- src/ZstdSharp/Unsafe/ZSTD_longLengthType_e.cs | 11 + src/ZstdSharp/Unsafe/ZSTD_matchState_t.cs | 12 + src/ZstdSharp/Unsafe/ZSTD_match_t.cs | 3 + src/ZstdSharp/Unsafe/ZSTD_outBufferMode_e.cs | 10 - src/ZstdSharp/Unsafe/ZSTD_prefetch_e.cs | 10 - .../Unsafe/ZSTD_symbolEncodingTypeStats_t.cs | 21 + .../Unsafe/ZSTD_useRowMatchFinderMode_e.cs | 11 + src/ZstdSharp/Unsafe/ZSTD_window_t.cs | 6 + src/ZstdSharp/Unsafe/Zdict.cs | 48 - src/ZstdSharp/Unsafe/ZstdCommon.cs | 4 +- src/ZstdSharp/Unsafe/ZstdCompress.cs | 1894 +++++++++++++---- src/ZstdSharp/Unsafe/ZstdCompressInternal.cs | 70 +- src/ZstdSharp/Unsafe/ZstdCompressLiterals.cs | 2 +- src/ZstdSharp/Unsafe/ZstdCompressSequences.cs | 11 +- .../Unsafe/ZstdCompressSuperblock.cs | 289 +-- src/ZstdSharp/Unsafe/ZstdCwksp.cs | 171 +- src/ZstdSharp/Unsafe/ZstdDdict.cs | 3 +- src/ZstdSharp/Unsafe/ZstdDecompress.cs | 7 +- src/ZstdSharp/Unsafe/ZstdDecompressBlock.cs | 90 +- src/ZstdSharp/Unsafe/ZstdDoubleFast.cs | 4 +- src/ZstdSharp/Unsafe/ZstdFast.cs | 5 +- src/ZstdSharp/Unsafe/ZstdInternal.cs | 9 +- src/ZstdSharp/Unsafe/ZstdLazy.cs | 1259 ++++++++--- src/ZstdSharp/Unsafe/ZstdLdm.cs | 77 +- src/ZstdSharp/Unsafe/_wksps_e__Union.cs | 15 + src/ZstdSharp/Unsafe/dictItem.cs | 13 + src/ZstdSharp/Unsafe/ldmState_t.cs | 68 +- src/ZstdSharp/Unsafe/rankValCol_t.cs | 9 +- src/ZstdSharp/Unsafe/searchMethod_e.cs | 5 +- src/ZstdSharp/Unsafe/seqDef_s.cs | 2 +- src/ZstdSharp/Unsafe/seqState_t.cs | 43 +- src/ZstdSharp/Unsafe/seqStoreSplits.cs | 14 + src/ZstdSharp/Unsafe/seqStore_t.cs | 7 +- src/ZstdSharp/Unsafe/seq_t.cs | 4 +- src/ZstdSharp/UnsafeHelper.cs | 20 +- src/ZstdSharp/ZstdSharp.csproj | 8 +- 76 files changed, 4026 insertions(+), 1480 deletions(-) rename src/ZstdSharp/{ => Polyfills}/BitOperations.cs (93%) rename src/ZstdSharp/{ => Polyfills}/SkipLocalsInitAttribute.cs (96%) create mode 100644 src/ZstdSharp/Polyfills/Unsafe.cs create mode 100644 src/ZstdSharp/Polyfills/Vector128.cs create mode 100644 src/ZstdSharp/Polyfills/Vector128_1.cs create mode 100644 src/ZstdSharp/Unsafe/FSE_DecompressWksp.cs create mode 100644 src/ZstdSharp/Unsafe/HUF_CompressWeightsWksp.cs create mode 100644 src/ZstdSharp/Unsafe/HUF_ReadDTableX2_Workspace.cs create mode 100644 src/ZstdSharp/Unsafe/HUF_WriteCTableWksp.cs create mode 100644 src/ZstdSharp/Unsafe/ZDICT_legacy_params_t.cs create mode 100644 src/ZstdSharp/Unsafe/ZSTD_BuildCTableWksp.cs create mode 100644 src/ZstdSharp/Unsafe/ZSTD_Vec256.cs create mode 100644 src/ZstdSharp/Unsafe/ZSTD_longLengthType_e.cs delete mode 100644 src/ZstdSharp/Unsafe/ZSTD_outBufferMode_e.cs delete mode 100644 src/ZstdSharp/Unsafe/ZSTD_prefetch_e.cs create mode 100644 src/ZstdSharp/Unsafe/ZSTD_symbolEncodingTypeStats_t.cs create mode 100644 src/ZstdSharp/Unsafe/ZSTD_useRowMatchFinderMode_e.cs create mode 100644 src/ZstdSharp/Unsafe/_wksps_e__Union.cs create mode 100644 src/ZstdSharp/Unsafe/dictItem.cs create mode 100644 src/ZstdSharp/Unsafe/seqStoreSplits.cs diff --git a/src/Zstd.Extern/ExternMethods.cs b/src/Zstd.Extern/ExternMethods.cs index 0587998..3780ad8 100644 --- a/src/Zstd.Extern/ExternMethods.cs +++ b/src/Zstd.Extern/ExternMethods.cs @@ -5,7 +5,7 @@ namespace Zstd.Extern { public class ExternMethods { - private const string DllName = "libzstd.dll"; + private const string DllName = "libzstd"; [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ZSTD_createCCtx(); @@ -14,17 +14,31 @@ public class ExternMethods public static extern nuint ZSTD_freeCCtx(IntPtr cctx); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern nuint ZSTD_compressCCtx(IntPtr ctx, IntPtr dst, nuint dstCapacity, IntPtr src, nuint srcSize, int compressionLevel); + public static extern nuint ZSTD_compressCCtx(IntPtr ctx, IntPtr dst, nuint dstCapacity, IntPtr src, + nuint srcSize, int compressionLevel); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern nuint ZSTD_compress2(IntPtr ctx, IntPtr dst, nuint dstCapacity, IntPtr src, nuint srcSize); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ZSTD_createDCtx(); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern nuint ZSTD_freeDCtx(IntPtr cctx); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern nuint ZSTD_decompressDCtx(IntPtr ctx, IntPtr dst, nuint dstCapacity, IntPtr src, nuint srcSize); + public static extern nuint ZSTD_decompressDCtx(IntPtr ctx, IntPtr dst, nuint dstCapacity, IntPtr src, + nuint srcSize); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern nuint ZSTD_compressBound(nuint srcSize); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern nuint ZSTD_CCtx_setParameter(IntPtr cctx, ZSTD_cParameter param, int value); + + public enum ZSTD_cParameter + { + ZSTD_c_compressionLevel = 100, + } } } diff --git a/src/Zstd.Extern/libzstd.dll b/src/Zstd.Extern/libzstd.dll index d2f94740c7f783852633627849c12d8067a3da5d..4ebbf8d9d648c495b1d743d095359512dc264445 100644 GIT binary patch delta 206603 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 |1!Y2v79Z{qNnCJyEL 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%0DGr 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 delta 155585 zcmb@v4SWcseho-YBF{eP=x6N0CA_xwJ8 zK1_P1r>DBQs=B(my1KXGv#g4pBi2`h#fKjI%j8pszUjQ2_wV~w!n5=K{ohXE>E3Vm zBOSGN@3%8}y6@WnPxpU2fxkcat&DW?se|97Bh}Y7e!GaLhrXS})9i03oqTFGPxZA2 zc>Xmv&o4%u;uVqjxf@? zsfT{8aLFZwKR7JU53pG5eS284sKELjmf9qwz8;q8m+~*nKO1{kjAUfL=&+RBH*`?H zO}NQ5L7D}cQ!FLc`-TqGomR(XuC1uii5FC}y&D+Zx-6EW3l=Z9zU+F7r8Wc*_|h^3 zX)HybY8`x$tB1vsigM>5?e@=y0vD8+pK;89p(Pgu2I`MkhxDw#lNC?b^w1lvgL|SG z7NBdH-b4S=IzS}UEog$pf%LaMHutwV1?#IR`e=JDCG+hQQ(sN7$lBzhc;8!)@q&F= z4rRnA7s`>lWud&ctg%{r8}B=^tDqZqZA0SmxcOaQeAhE+aGK9y32J_~E9c19qO0<0ce16u_ZBqMV*KG~vi{ekD{WUJ{UT{-<||b7Ium@F`LLeXWq&0__at9v z`{2!FeQxqy&Ieylw#ceYu8x)WFx<%eB>ACikeCo?j68sE2!6b84bMv;e2e*DLY%uj zMSm{kA`kMO3Ip}BFJ=<*5Xp}$4ai#6;*OT?Bj&Zg> zBVFyd*j9pco8xNhR=n;}XnXgyWc~UcV{BJ9ChKc^jI%Ao^R6C~okd5IEsC0KBHZ4R zth;*VrfwmGr-IVkwxs9_dRADGeB5)4wJt?Jw^xvoqF(1w67F@W;Lu;}HQ9E2XR>Zj zooqWlmc03zRIkNClk*PoLj2z@s{AdV1huWn1$W6r69ZL>42r;vHW)?`agOj2*N zw3R?Aq&gBtnH@5et;zbO=YA$wHV^MpBG{De$@=fp{o!HN4oicqC0(3IFHfY`B+?ra z>0OERPl@P(iA6Y+ji8un=D4cKX$z+ck5RG+mk@&6H*Z*#h_MAp1&Udn6W<<1vYK z1Xx>;y(Mwk4zQCUE!{7*e4^bUNUUCNX=UFAw1T+Anq|#iEJtDv+3R-~i8Z6an96t@ z0xQ;>hx^FM)FtVPCbh_Hk=rNDldcO(bEP@+u4|CAP+V5$xN~!kFC=^>EtFc`XFuS6 zkz20*yllF{jw@`x;(1%DqYekl>iuc)K|iW4By@9*GrwDsC3dL3%_gbIlDAo`?gQXCu>fn5*n55! zJKP>=u-T3Qfl2bqLgJq|#AjJ34yghf7K;+C-;gvStV9~DvTc_f`9df^0I_6o)*Psg zmrao4hvdUwqVIKnAc-$pL)tY~neA1E?~!W#Ym;Rybs(}QK+B~67MaTM!)AWuOQHNw z&T#@%5Yi^51lXbVDCmoaG@l*q9Pd}OR6lFc!UX~viB|{2mm9?wV`89r%-)49pDdI- zaj91RaiOFxJE=t4g!1~Fn9M-uE)H}u02ntM{u00 ziL6a`7Z)k)m<+Kg3bCf3y1<>Su#QH*yO=~zW?zwHxkzI#lC@BUvF1}ijdawCHOnww z_*u}sNQ#_HUXlvLUISun*Yj+^m2Dle=O}uDo;h@2ICVxZf@f>mx_~OKZ?h`4Cb|B7 ztL)vo^ly_ido|H3z;?)<=1E$rOID}5vt(^X1)=7G_!@J6?5w#B!_-14vchde3xe+4 zp!$saD4H2$4MFvJ_XE7eZwA%v?)A78tH#?cVZ40TIDV=V#5#b05M;V#t#@Cy!@{P! zeId4=RC}(iC5WUj$WHs2{Xw>acWSm*W-HwC_s1gdUlmmS?h>Wt%K-Z#G`u5n%C~g7 z;{9>)IY7SNu*sf|$OkFymnq)UW#j$GV~3>rafXc)f~QIG9xQeE(R<{8XBW@vn4sBr zQR9QiE%7wV>@s|$qB1=^Y@l$p{;WIQbpb)UNMc6-t^QLRk%@y!0phacd0js-GCd8S zNWf-^L#nd@UVD-L)v*4z`0#0YZ+a0J6q6z`Yx|{LZ~{58E>OKWD6Sy@kg_{&-O=H< z5kedt8O$!qIU;M7KN7ayqvHKL(BC1a8)DVXV$}!w9m8+6`Q6$2+rx+2f=J`Tr}ke% zVyIRAl=!6PG|VUT4tEnueSJ6f4Qqu_2~haiNlI%|V9x3uX51M3!1fVztOU`)siax=ZAi zc4hc6D6526{q(P56+a_ZXQb*-msHiCEma(wgj8L}`54mXxa~pqv81J1g6Q4Z-yaKT z=}ri?((+kw_yTtc3Cq$M3RJ#R0y|8hmPo20hw-vYe$J~yrCbj6&qwwr5|ffYTM!9U zAAlkl32+d-c8F&DKa1v}(z8VK@Gpwybcp62Sz9&Kl+6?3{m|A4*=!Q4zJ>S&Sn5MT zwp)K}RA#o)Vt_+wgTbNH`|?@(a!mq)i*)?n?+9BzG%^DGm zXYv|>zJj_UKlsF&%TYMNyM`{_eVEdI1@X=&F<21YbjYF7e$>xAes`h1Zbatjqi4E% zw;l%^J^~4OKL8CO!5j5?qeseeSM?!VD;JxxUlNrI&Krw*3y| zpx$SdYLkPWGU}KUg?1OmhI;JAvtOiOEZT~(D9FsgAjmq$2XjP_nMQ-4oU`9}dOfBL zl{;yWla`AYH?UO(? z5TfM+MGKt{M|NaLk@}2)ZHMeVCf3XXWT+N@t*zd0)aDB^3>B%~aN^_A*8s&vP_h;- z?2=Oepk`Kh>mee5I59w z60Nx$Z18(euj~;&aQHNCR(y|sRt7_+TQubd(g< z@>4*Y2SE}Cw45IlvhAqvu*nZl-^!M44Q&tOgM?_dt;c;1%<`W&2j>uNIsazjj`;Ti z@vlYlzFXQC{F{?tUISU;$Ry^Kp`^*YV~a1ASojS0z-%y$*hlP20Yb#b+5)hwk+aP2 z-M8c&lV!&i59R9mS9!J16wfZ%)<_1PgA6(+4bCtw&^@_nA7ta* zXS#2~@;=CSp6u&h~S? zyO#DeA6b1nO=21ss#b`EU5@OqhCKDa|C_Ir@wLW(3Avu^zSduO@DpKuC_}_(V7fu8;kqR z!5l>p&fWa`apzhEar0A`{#g*t(WmBqBlOn&d4Cd8H-D0cNoX&~(V@WgL=wetl zawK*v1V;+zkyLq-X0Uu>N;BrRCOr9Bm79is?u?N!K`g<;ZnV}C&}6~y-Lqr_adc>6 z5TjYt{a5y%b-WBb2BU*)Ur3u^?KZ@TFM>YT2;@@`Ukut^wUgp}CjT*}uCPD$$=Vcw z7?y2Z{}UPyPZL%O8Zj<8mLGJ;cPgYO+^ICqmHd(Ixf!bf{_t*L}v#XDn<#6B28Qftw zO}A%zB+Uo4)D-)`Zp9ub|5@_QQ=zpb$Pwi^J{@PaM+(5nUx0!={p$6xCr zd?C+y-o44D&%DZ;3cf+`rU%*AhDHD5RURk3Mq}pQ%^Oo>(~n-|5v;o1cjYK5TjkC| zA|v&gk1`T5t;+38q}f*eX5Vmpq4@^m?=!vuJ@96s?P0sqsvq{=+Hk2lnhQ<4cxT9 zlNXfq+qfQ-7Z_1C^y!F zWstV|Heyar_&ap+Y?1~p#R_zPxfQ?AR`Xi~-Tf={#_N+HMl)Aux@hwfZ?zUEzyJmw=@7Is_i|GnY{^* z5&}5-?rUJeAIZZ7H;k_4_MThl#xNKW@C1i&Kfpt6 zX;V_sN8s|U%LLDiCMV2<25@AWTBOEhK4l}3)f&RqSWe)x0x>Zw)Qp0>R`pTY z8pd~7fmShXT?IkP3Z1~)^&IHBjYwox^8h8Dnp#S(sJjBCR%P%v{1ZQ`d6J&A$`g=Y zGH1rg-kN2|TIxaN1XX57amT+`Ic!;7mkS4}tks?f<0F|JZXb@mx0}!WF*v|O`w}lu zSx-Rmp5VGY{*KfdR1)paJ&b%bzx@2>CPrJ+ZC)W_>sbregQiSJFk@V$u_kUEd> zGqGy#*#+PYuwIR3r|K`xn4iuyHaiC9s>Dl~shd*u^Jk6@UsFr+!kS?;8TZ2C1p9Jg zVp?PqGLXcO$ZCXMSELBkJVcHq6S`U4vzE5hq^wtH%l_48-B12pVd%6emP zr8&HbiJbQ!eY&)t?0t~axwqlQOOS#iYV^kg>Ne%t8OgOqm0n5b+df*ItnZ#R`XXvI zO%;~CJi8CDa;j$gSGY^a&t4p4k5LOawrE0SbE@u|-9P0b)Tl7Ke&y_e#j)~p+g2|V^w@7)VH%s; z+F=BBn@n*gXN%6H`6p8>Tb~40+%W5;NYWf+gNc{|7*AD^WF3VjDo+OWv#)I+bwi)X zP`(WqoW(3~9aa_iO=M`yAB%B|WCNdnE!xr|8}&!79o&Q75w$n!yRIE$I~hsVzrJ>i zg8&(W7ANcOIr-*8^PS219dkzZngwG5a(D`&sQc!QD?ZvERgw``1SnITT*6hGn3fMg z%lF|b#8rf=1c+1sYyuz1{YE^of(+4_0_1RySNV28Dab@4C#S3VnXJH#+kBUrpQW#v zTacFb06D&@tR*+&%QdTfH|zhHyTjUK(Vx6-YL;W^Yx4XZveq{>iZQ%u)wo+YU*fHe zQYtlFVX+3>TJ`jKZm-nnO9g{?=K36fJARYJ@+Gdx#TJVVSASd{T$kaRhpQA<4X#&; z^?T;+&b8o^Uby<<%ECn;etvawHnOFbdS+1TegE(DHASwT!CT2TUsLJ3Re!PQvOyV( z%tt3u#kME@K6Fvp)pBHuY}Fmt=UnVqdPsf%Umx25R|~7Ee4Cf6kGCq?BxhR#ngI{T z|Dv^j#6_t^zw`PTdd2m2efRaZB#%RFpWmW4-!M`yyx~&){u^#_Q`UdF^z8ZLE*#Z^ z@J_0~A1dGh*gI90*bklv zxNyJj1&3ZgKhyf&Nh)Q1)S<_U)Ab$;6zeTKXGm(d@3?`J`0kq&n8ow#`b9VP*B@EX z!`jy@{t~h~t9hF46U86p#Z#$xubVnii*<0+Zs_*S%x>RIO3}yOIKq0XS*mxpQbM;< z-zMvoH;%OSHA}sOX=_*gbl+Kjvi|0cL#;c%ry7SW>{jZx-AYZty*e+0V+m%M1iXWP zGcc{&H%Z;TIhmxdT=%W}t6ZKz!dv)Fd4*5@=kf-u*27X0$WP&vx z(S72ZV|M+IH;u5K`j#N)KHRO;`fjDFaIelQW)0*T^6~XQQay7UB0mfe*DbW`&f=j~ z#$gDP^;e7g>+_1e&acq-w%!jRO7;R+XOm=|gWG)W>Oos05F0 zy|43wJIo>%Qjv6h`OV(Z4zcDn3{DWs+t)xW=kGwDsHj>>;uwp!Zfzs;oW!wJzwnm6 z16T0@*_6g5bf0MQEml4CmSMtLUWnU43A$hvwX0BuRln|*2|^|>MBeF6;yp+A2}u7V z=*_o`5_aj2-O_i+P2CD{he)CjOghlcr>EVLuKRB7A(ZmceY=(J(G4Umbg1)IQ0bmu zcD83IKIsQbr3;Cd=sp4eX^Xz^))B&f{i|F1^j*5x#B;?h=Iihc>Z4V9^R4~$@r!y0 z5wpzbM46$9G6U2{Yf+}3zVfyUHRN*>?>qb0R;`$@5c}w&Q?%X}>Po);iH@I#n!+?fsA}+DqV!0mIi@4(D`p36l zK4=3GfGhBGYfUxZw4AH?isicZj<tS5~gX>ycH{!Yj*E0R9+Zrz)jLJvh z%EL7QS2nK6xTfPO!nFujLbI1Ihq5Mv%~F1ch*`M2_}V;~t=dTZQ7bng$%!=pL(C-O z3BOI^OL%<}+1%-&R@MpnTZ=QTwW)f?;vrcdLB0__aTol9W#y66LfLFNvW;72>E%>qOX;W~yvpy&&Z8oJ_M2qgzht6ym7zPA_SGL-;<5V7+YfPj`V-{(tlKH+ ztKYQLW4+6~eQN0_>-bOfBTL6wlRwcN%jR1j`&hqi*_f2_k0HL3?fRX|GV~Xhi75sA z*+I|!Y-+w^YcW+!vr%IJf0f$#Ds?Mtjl`>Vu!~K0awxv#qV?sE^sASTwXXYUb9niM zHtXs8H*Z?eR|xZ9EFy3aPKaW@%*F;FF3Zo}W&4zt4;8i}(wv zmnQky8|^9dMx4+RU{joR6g7!Ox1dfECx=>;Y36S%<^Rv24g!VJ`!7uuv>L>c9Z7P!aEGB5NFa z5pF!C<$YPp3|9jX7L4zSN#2+^p^3qx{9@CXrVxg*mXDF&R2@T*&A}jC&v@E{xj-L^ z?r@+%^=&S+4{d6AuF)G~ca{fAa5a!*D|9nE)X)ueDsEMm2Y!KUxuq4g74 z9u$*pKo>)0cm$sZ*svzu035O4LWAdQ#Re}iEh?kin2P~sr!XCMft)>XQ@IFpn(tx> z9l}r;(xkYt4{or?-9b#DGMJnJ4d8Q%Y3P3l4U*0I!96r_gSMfGTgkQI1_0y!Bc7#A zE20MzG=OFYG)XWT$stRn8}{>FIfH_xTm+)>vsOM%C9Q}iyqwj|z)Exq@ID+z^@>y4 zLKZenRI)Jx5Yqw`@Fv0X53DEORJaUx8)Y?cf=NIZvrr!e=B)q{G)uz#16d1Da1qLF z!bK38akZijn(s`Hbfm1ffjge0NXMYXvshsJ79YN3GwQ03GzK;i6iM_S_N#%NaPRo* zS?GB+u)TUud77lj^2s`wQ5JaQCN!y`=kw-b9*MO;Prct;4^dYGF)WVeA6947!EkWV zI)Mxybs>l*I?>T)xJ0MyB+5rurU~ckj>?R%%qlhlq?o2w@T77*5-9tPlxU$2<4gFP zz#q;W=F2eogTMllKY4Q(-P7f`hWooacYasFL2)cPne){T-~-c$?BqPQre8t2{$ckC zVoc>u;?LX$eckFSY?eJK`k~eLSu?-aZ>Sk2Wa!m3lfypJt|gJ>L40)bEZLaXAgd3% zd5t7q2oMA=>`im6BtLFgPsZb0sn>UF3%bZLFiR#a3JUOOk5<{c!)zM$d$C||-nZd!5l7SecmE<%CIrULnxtxwek@1kt3S8@PZN#-HA*XJV(}jK(Xy#%%hXvMN%8d#r@)mk!gNlsn*tJT54 ze-OwM#`Ao^McW7rNQ7qOL4qoG60PQT4cYpd)jnI{E|Q@8g@1AZS_T2SVU``m#I-B% zS5a@ps^9;1U?2^t^N)tqTbn~_X%iwSUA@pBH3$9%*6X*@rjDH}5eWDu}e~bR17{#`}JDRSMuvga`gj z1?~onK#faNWcK^n`m?nYY)@U4qJLL=*9afbDo>JGWBWNUz-H5E?^#ty3A;B2lw@{) zlc4`jz35VE5t_t@cZe8#;15!pScEY3MyMHZHj!16vj<`t*_qYAm_`3l&AgK1r%iP# zwuAA$^R7&>Ok!o1InZw+r)welUoEk-lkJX4Z2M)id6knL7#7Xa%9Sbl%zH12eiBI$ z!`}tiF)6YvLkPiYV{f5rmfJDlrb@MvNzvVip+dhV?MbgEg^67k{-MBv*E)HA8fY=)oCy? zr-J&EGXOKF+3%CtG!Abx!2`QZtl<3rRhU5v9X4Tdkog4C*9j8nP14d;Lh)>n;eUsx zat2Splua~qeYvICV)aHe1R*j)nFn>afFU+QG?fm9Zp%3mWHIADKPl%kZ+QDT@5JiM z__wed#5LiuxJRQ2voSwwEGYMD6hwVl^7(v4$eW*0nrQfL2z(ckx;Ozk{C_68qr$}e-$5_=uMscq z>J%o?Gohe3?#U#}?P)*bCv=3mbQz4`WURl$K*O(bKgEeLX&spb5}TNz)*QuM#ogJK zay!CFDejMq!04LWZ36|rp7!TH(d<{ONn!1pqQ=S+NFVk4obSxFS{Rv9-o<6J{p=b$ zk5luj*Oakqim`0#Z_Zn~s2qx7kq;lRsSC{UYEt*{YU+Z#r6pum?I5%2usC72T2SIW z9K?uDrq!%sHnmf!>HXGc3{FwJZwAE)JESdST_6}E(j$dpoi5; z7ZP-j@(c4`szCMSqYzFS(ryNpQedC z;zsO#wmV3XEST!kva<_;_X;~Ag~i{+j5-Q30qglX>|fxGMWOQKc;pGdkeR-W63y-l zGQOx2r1)v}05U_KCfUPk>9x0MalguK^Z}9Y9y1yTv$ytSn!;g^fcM~HC)N>CW$oz_ zlxm|ts3t5{XQM(GOXg~l`LVS_DiO0)RKygx1LAazS zPn+UuwBg1g*S+yV?5|&p`m_ioiU|GitRrYiG2PArqy5-qSU0u(wNY36?KvLtP$bta zza;&xDXW{Vr?({_9C3+4_zP+A3r&kO<7>(&7PGKf1DLfYGL}&ZG&XVq84&R*_eLNg zrs+^dNFKsEc``L6;?heu3=R9$2l$BFSaFn`68MLlR@^e3*b!vBt0Ag(<#`GInFgFG zf!%lp&6xy0Li@!iMsf(FLGjj?rkeak>|;ZFf7#0=sWb(IGX*7VmW7}JyM#(qBe|e2 z=varmp!&Fsr~iyVsQMpgBcOP8{$~WjY9|6@UVefYr!vyN++WpKxO~OT7JEf@XrOZvlcRi|9`9H z*D86EVo#aV6PuT2+ z9IPJfCUJ(tU0n^#Wl25@z=2ZzaR@%lnw0s0L~#V?;c%34sS1Y(_fW8 z?mN(uk{vXY)oIcWel!DbBu6RHlG;<~U8D(5jgeOTMLTf!k6*a!e5-Cg!Aq0TMx}Gk zkF}3TkPTg)#`pGf&OCG5AQ2~1xwFm^y>EVUpD-q&2p^m=OPWcJ)NIo=)eLY5o(QqS z?0)WgV8>uzSM!C*`8B!pR_lEacGw5Ksr5^GBd|JNmI5DvUqHAa5^Gs6B-s|RDj&Eh zrW2C9VGhN|GX8W;WFG@+Y6m75u7n;0_o##ECI<^5^X*WE1Qd9*lc>H&m-n!_b+ zbUlI6{=AonpygP_INhGg*17QzVyBgsPuM!@o+0>~Sd`GmhJ;phm1W>7dZE>LB$tfR z=tjJnqS;?Yf=5=1*G2@d5LHDbTRz6}24fYthr0z`2H`|j05#${+@_`e7DN~`;Uedq zQ6}s0Y+76qQXt&u3a7EzxMwxwXHg@I=zd)V-LqnTU)YE4iwG2(irEM&#%-GdApmoJ zE(o&I&mN_=|JU3P|2ORaMQ1l{9f6U^8L!lT;o|<(>LX>DSQv`5*~)rir+8TsHVBmW zjKG9DP4P#Co#K=89iKKRmHDU8R$u8*+A>#~q|HojKSzSUoxLHx+$07L$9LpScNZ>k zwD(jZr?B6XLTPC=U>xlsiTsevc4M2p{@H_`JT6uwUw(D&nmP(i@ivp#PH5(0h#1jq za_htRLkK$@`y`frbkwr{7g4PL(p2qB?_%&d5mX+nQND;UOE?Ho|cz2Wz zg`5-;A9u*MdWb3U-BYhd79dr5S(rNXzNWCx^*oC+np|}eVl+PIN=A#yc#`U9l4ESR zgv4xjPiK`n4m)9)@1(7M z1aKaXO#lyR9w)Qan?SW7P{qPP3~y=Y!wF=euk!-xGwvp2zl@74=XJUGN_0*%&wlW; z17Lv}1_`y~g9K899U%yBg$w1NfiM%%+y9u)#bCb)xtkX0++YEOw&NwMA%_vV{v6^U z0K!90n<$D=H@Jw};J3%$pnLj%%fo}lTt2N{T|v!LS5uG7`#V)ro)lu=ld_26A;`W( zCui$+^7~!T2&5s{qh{}KmD$=ZjWL&Z`DiWPLu0%Edxa>umY$6X<~!08SiWre2>Enh zeH`nSU&{3#S%YksaUL(Ux)zx5hXTxAPk-6EA^=v`(qEaaE2czU%M%vOMFHSQiBWSgPGmtWK{q+s z-0|oFyEXf2{kkWH*|M*5=#eKbv~8N}&|i3BuEX!fu!oIc(f12%eZ$-NvI&l`h%C3m z7QrHBk0ASi+Lw(Q#b``o%QHiWVq~R`5c@L7ra5H~wp~rjfZrtxMHJ5wMXPuoorsir zEfE!OXy8;T8;M_dYqwLbxl|xAMWzxD@K@mq2PWV_*kycRMLtP3oNaIyRJkk6dlxpT zCDXIaX#wAA{b?ILlSHCYC#TIb90Y6?uFeKPot33xewc{oG`!xHQQ~_E#%7a zA)H=T!_(d9jV(b^I&?-tmLD60_@06{5Ts|?geh2<#L_by;JavTjGaWbeI=b9L3>x) zj3gASJWaTkUg)d6NY<7M2#hF~Xj);ma)X;FAl0?@2EPt?z%S3~w!h*#DO`s+Jhncc zD4hd6;j6t<;O;SOdbC&6VphK0y%BIRy5dL?!;R%3>6PKg@T0#LQmZRZ^Gy-ch=QCt zPz3Y>i|>f1HLSNH_8``NE4J_2#FuvCe2|!|KEq)yR3%}z5QgV!pRpf{)h1vW+ekWr zOW!rx%DPrSv6JX$Rq&l%yq?aLYNG}9`5`+d3LCgzFvLEg?vcT^tgn=|g@20tVmI-T9nv@cPv0;(D`dH| z!2s-|oLm}(ASR34S-4zAN(sK;(}tt4aRNEtMSijdMgRARpQsxGj3A-indBmDDhSiF znN)Z)5v{2`4-@~g9tw8Cgdtb$7ynD~Um*C5TWHUC69Ds?67kJ`77OCm1*)=p$r~bT ze;^4&{ISZPA(_O5NJl29SqOW!8X=kD?{|PFGT0C%(t{jb#O5$Mi47lIg4kJ$aF-x9 zfJgvBNR!Zm!d=vs_}&msaLOyTlSK$%-cX;AF_>AhI7N= zhh7{sUcHI;oGZbT&Z0l-3w${2{JM)Z`_UE`>a`dz7ZY2}+PifC(h9_w0JKSE{oC9> zMSmgmqzctE(388>sed3+del=XL?7Nmkn$cu`WtHNH6r5AQuuUhtIzrjXBb5tL3lRelQ5*meAR}pvjCHeDemhy z8npizIX-U2QHI%;{yI1bZwTgHX%1lhof%V)8xde zwBp$<1{H3fAjeAOLmO8Y;(sl_1)XyUOac>xRbmk60E+3X)rw6rX$yuSWiB3hG zn4z#_B#wYO1(dI(0iVru!@779B4c5d7C>c(JX0L%>P`B{zq!M1T5!SwRM(2f9&Wyy zVH_^FF;lqLvmF!q)kI7MK}&G@_wwh-viLhJ;O?+uvS!*B+)K_X{(-90O8WcrHwJ%0 ze>>lVS>mJ5_$&I0S6M(C)2j?5r5C~x>!W9laXMAjbI7DrX|{)s1QxwvybI#AH2}e& z7NMa-CXE}&bCfLuJd61~`3tZU!}AAHJBMTqV|WX`NMS7&8Okhl3c4E07n2ip5s3 zIqqehhIoc)h_B4Vk(-f~FvL^1CZs*|WN>3XycY@jJ_5|lA(*gKg7N(LG1^)hX4Bov&;iYf2Ebvn8NUZe8f;CFGU!W0-Asg`3E~M#Bd4~O zO=FSQ^rZ(c&i*$7wWB>33g#!Y5q#SzYx8W1_dWgC!68}pfz;Emv15%XQ0Si5+mThd zlO&D@r_yOa4f>`}`a%u%pv}xB{0N8>kS;zRK*abWxZl$(RgycWRb2fI63=o6%>b8W zCL^xNA?oOPi`<2tChk9AyZmgIgn(ZJ0pqV$7$5$or=$7^4uROgUY9%_tPztYS;%+b zLex>Fr!g;lccwUT7wrzq&%oo|8T=_j#-Jvv6I}q6+VX+SCTAOeN8ogTP0aLrca;sL z=wkLdf8#@RUNYcfckoz)?+&i!A=X)tD`(3`R3n?5i>=MKBO7o58z{?p6L~C?d!ma? zcIU)Ahw|=ri~03xzPmBsg_dFp5Fy#6fBMk20ntrX!LC2ioRPi>M5F_g%n3t~on-2E zy}5Y^LXg_{zW8&|Go;9?Z_}q`L*aZ;v~|t+2qUO*avmYE!Mzdh5)eY#-^n__wl8=+ zy{CMlzI*ryCGumkxauW*C41itX*Glj!lp=s#r#G^ivJC%nVqPPR@S1a1GMp7#J%f&-Tl zJoS7it0s@6#u~0;ITA<8h9)9b+kCX!y|IDj_2_EtqY`uc_Q5o*p`n7}BSPL2OH!o9 z6q`l=uw}$%=WEGU>&Jccj5o5ng7~wUfU~Y}j1I#Tlr%e9+CzpnuWbhClDERWtjtd% z;#EPWk~RuzmBc<)S%e2Aj$!+CQi2M?DVFn?P@H8$4C{~Avd3t&kzNnlPLL&DNRjO& zVZXM0Nv~i@nQLDPd(#-D)0j}6YPvj4*L7ljwCr4zBRh(M`cd>p>>@-M+%j~9 zL*@C3X1@&f7nf4PN7i&MpeAVJOE47Wh})E*ZUtG zg}+xFy*lc;F?o1c?wrvC=`hi zui_Sy*qLq|of}|}G?O1I#1>{t?DtK0qp;h3EuSb*=7dH#Ywzx4L@L5GxAEc5^TD!+TxVtI()b)HPi8X|_hY)Zrs%5vz> zF#3~5{pfOqS#V(29!327uJ&9!nD@Ke$9CN}wnHv0GD6ub+X%cY{vBr+?ZM`jn=Q{S zzw8m%>2iyq#AAwo-nxA;Bk+S!tNk2@hrkQ4~{nmuh%^T-GKf@!Yg^M;!=x24}s9#4z9u} zJPX%v!i2K{*BjlgArOr2mmYj)h;6{t4*m9buCrQJ=m+1KVLe%(k9c>y_4q=4{=4I> z)dl*4?@q8@zfgbY-38X;6ZQP}CRks(QLlOLgl)#RN%~!_`>cQI&eY^AYCfs76*Qjm&(WAqQ{=3<3wQM!H|HORg}sOj4_tQgP;+7 zvMm<$#ukg1W5>&SM7B-GTAQVOgcP~UZ!04mQwS&_)`WE=uBcdxao#~VXNORT7?$6x zN@A}cx(+R%PI6+tEn~)rIU%jSPH-hwUngPhLvm3LmNEG1>m(H6tFIG^605KGR-p34 z>g$A(#OmuR5{X|}enb6mIV3P7+>AGH)QzdmBlrZqeKNLXElO+*YU@bD2DKVe%0v(x z#e#WKzQfN-o$VOlFh%BG7=HK$1jmQ3Afd2DX$Xfcx`<5C>7=ZqWPpq|KEafL>RXE7 zQyO0gDz8(cgTXGKe2~K)d3D>}^H8)}g4S*!NfYnF|FejtuKU zv?0Uv)6l*=9G(S|^2MDZAOMT=sD$$XY85q~{2~%IXwWlurl;jUNq_3-PZRw)3j1aa zum@P2=Onp1%^muNulga7@nLEn{!!f$9Xepn5$F@th6SdyHSQ|BCoe~#(ez}ZsaVP| zWddyOBofYACKBahrX*`T810}*vd#g_82wYcUz~_Rt;Iati8(mt;+TW$YpxhXk_@K6 z&x=F5NRC1D5}e#c#CJh_N&K{lar1n$Y(Ov;NI+~>4Dc7>5z=8UZ{+Nua#$Oaot>^= zdKE(vERs|p!Nxgckfxwz6T${7ZecFu^r96nc&+qs0uPj0P>NC~rA`YrG=ZK(K8y}! zxFx;BgNCUPCz&6cdCbw7uhT68tvEe5pd_dCM%-Y9@fjq5sPCv&U}02H?k@;BaD>gc z_LT$&=Ht*AQ?S>OU=yX~gme??zJ!RIl52`8ADI&ZOb!UfW!%_Cdx}VYooY>b5n4yP zP)yO~f@|f1E0WOq=?Fs{x6}6FPx{lJTxrcYp|^iBzUOuzkaNV(qDS?Mj6TBKdcYVW z)af@G*Yzhm)PkWka@xKaV!Um|m3BGW}B zHsJvsj5;9wqpVmWCBdWT)vUEED>`5`?-N}?}JV$#m!1b{_MN|uZ^GC2$JmgYN9!$f*Dco~T? z4-7DF`ZgiP!!a3BBP7V&;kk`;Ff21}6qDV25FRmo{XYhWyHxP4a0QoUbp2!l)G^VK z==lIr(!yBNnS?b6>h^2MJ0iEM4sy7xpY-?P6!3)~Cf5507apLN z#6JO3jW`D8&>)UcIw+=9*9!WC&nG!rAAvDfjT4nNd_Fcjhh}}Krw9{f<4gRg26mRx zRym{3jW)o?H-s?XYzlI4pj_F2i*jQ%m85@MufTjU_X4L?w;~TuygR2=6GJGr51T5W zo5|*abM!Fzehc-hzDT+)y(i|{!+Z=RJ1kA$Cn} z`4A7e1YcWa!bb}_x6j&ZXj7{GUVK0j7*aP|fBD4luy2~#vuFcuAF6dcay!x@CF*l3?r@6Ve*TbI8&Z-(bolBIpn?)*X_u`LML7J}0Bqbb+I zcuAAhyB$DqBWj{`{0gM%-A;JIpm3h&4ho9op}v5l+$>n=255cDSA*chLa@W+jF47f zZ;XtE@eHg{W}2%pl8q-jf1;lp8iywbo?r`*t24`wsP!bMJoRxG-ry+<2@Dz{g2kx% z8?l8>mV$|z5!A4u4t-SyV9Ici4+7;H{bAlEsS|Kbi89$TLkI_yBu#i-Qe(kb8vr6T z0gtCM4hgVBjZj=2={U22r0XhBv~w1A7K!-aLw}$;(XAb*9Q|%qOh5E>n*NWkZwrsk zKMFpkO*W{vP+$@*2=xRwh&7dlhJb!JqiLZcTFEB_0~i_~Dq^I!Q$>*WlUa2I)p1;A zFH@Ihgn&_;u6LSoVNf>qYL)GK`|IUPhQy_1u11ExQsF(9n@GBMA z5MO-)q{;i^QD7-n-GN%z@7Cxye&hC^w)jA@EjZl^h+uWKK@lhJQ7Yl6vs-XmkMnIQ zlQOKG8Or<^W#3w#i3R(IZlkW2>jKW}`9>?~eb59(3phngMw@Xq;zRn#Z{1;v4a~)$ zj84Y_3#wyc8t%-}*r;__3^cL8qUsnFTQ>DT{#?wTbMZWmKbP?5ad;ljpUe33cs%>~ z^D=zxtB&Cr-etgBfoDIch}%MsctrIDPH+*00~@i|uDUOY3pywH9qHWk@+5eG+8rTn3KZr0rlRzF zcTjdiyT~;f1^Cg~7Wl=bpt}GDMhes#2PkXLk#19LC*aX~7S)yYl38eDw_R9iR#M)C zm{_HgfS|7-oZ!CM`orJPQfT^2>rGia>{rRgk&zWN%ccEDh%g57I3_NwG8e^ApDOd> zChkEkw2AB%El`9=5&epeS)FqN^sCAWRE3o9K2prxM<}_8_jLS12tNSYcr39rh=Yz{ z?)($d4aXa%wlvi6UWK9N<#+f{LlVfBHnX%FusD`RA9!-3ZNkkd`pYM; zv)&ig&pUN-FUZXbcyvJgP5MZ2c zH)t@d#&IV8chv``>;LoP7=3bis^0(PhjZTBdrNGxUE%|@LI=A{cGN*cVVh_)IAEMsZ1}AM1O?l3 z_F!k9HWlO5``5rpX|J9=7OKux|6N0hSWRd8!$Z@D$D%;t)47pE>0I=#V!)%pg3o>_ zb2bjHE~1RNeoVolJtbuZ@ z2ux^gyNn1}`2+WL1%qsoW3pyf;P1k+{NnLg>xh?K%I>EQ3;SU#<^&BOpNj@dqLDFg z6j{|y2#U|i&oIG+3RDA`+g*u}Z!^jtB@){Kpa@U`wj~IRmKD=%vXGmq*p2`S!`heC z5I`nqPn`%C6n>yNj6bcoI7t)Md|(aEd{6@quo}V=?BJ$4>PrR)oGwF6xCnD844gPA zXhH+5#3f=JKe^W289Cmb#KjN+rT7ssZTUn~fXLBqzq} zM#&;xt2uRHtH{y|BXdUC11^ zyEcIZHuafc5udToh$Ge%nvhO6iypNLx1W1yqMTS01*(K`bas+(-T+c-P!QbW0~=&c zP@l1}Ho7)R2w3-2MPE-6t{*+ICQ%KRO?fqC*iCQ8x9Dh7v0I~ID=97Gz4GY1WMPnX zSXFdIvJjfGd3EBmd7aoHm|&mr84v2*jpb%)8~x*-)2qi8C%!np^NaJaG-#42+CN2@ za!LPAm{U9HeiaY9gE3=F2H>3I*pJS09wq47v;~HJar6%vIA zWE-Chid6eUM=v+DQA zWI7>2L4clJ=!}@ba5QDlHY(Gej^|B6w4{e{MIYXfGG~xIUW`{*4=hQI#(D_nT};@) z`w+A-`UZsM&H%%B2did$lU4f#6u#1}R8;IKWXv3abpiNr6DyBm&FjDfYmd|dK;MKX zzq)x1eWux+kXue3Z$w7Ul63Hs(-Tz0K_ZElDq0p@*;5!UjE_EsDhB8Wz+rP`Q>>Xt z@T=n-X!J-=;ll7TfC8WjB=oN_LgEIh5QQld(fmQSnV0hH3VOa$yjb-)eTFkkH%ZYb z+b~@}VC-w<%%M+B&RhttU@wFLCW&YPy$E{0FTV+_3#z({KV$zrvi*5FHMQJFQQPG2 zB4nZ)dkF(a40}5vrgtjr+r|hLLrXAUH@LDUNq)34`d%+#*76&LynCC;V7!nXM;%h#$(7J+(CI;#@j3ZwHi<3zJ`_ z7oir}!x>ecf@gsI-&Eo9D~g77PRUHYDLI4M)u{6rhWtrz;d3V+%vK)^Q|#V8fkg>g z@R4|QOLSmwVR{&9N<9JaWWufo5((24XD+u=rC6j#!u1AJJRd>!l+8m6nvrcOpOH9U zfsa;x#+rCx^GF5?^Ton8L4oecA|+G;K%olSa^Nz~q39KL7C&E{+v-Ch2VmJze86X} z*xt@jP;sOpVZ!JQd2M>IN^`hf6p35Q@k4Ljlq~O+2s@@#oNcU1(n zzMJNyME`J(FkZU4`mFQMfL36-8ki%d%|&oR%8GPWy%C>AR{94?V*W0bKgq1Wtlfn{ zCyt+kj?NT?abb=THO^Sok`S4x6h^H+P8}1;Y$X1?$EnLFWC2a^HqsmyQ&DW0GmBN2 z$ucUfK29>_SD&V?L6d)tcuPL0cXuaoe=~I$4EniBw1-o0&F?@tvcx8J@nSj|iV+`* zfxG;848~9YFv)MMxe`&JYC`g8o`l0S%nhP#Wf+7M){lO7p(v=qg3$t)8CjxhoWj-J zCOZ`sKnW64Yf4PTM#50T&$=S;3VtN0MRIe{_e2~>c_*P<#<+sc5Y(5+yKsi9=bBa| z6bp3#cBi8qr!hRvAAkgo&Kz8f7eFTAo(6SeJVSQkwn>QIohB3v?Xqt9d@L~?lg;T^ zn-w)ijWnS;OtiI>2PO8J8R~-wu!G9;urml4rT}El$ci_)Jo?$W!X+1(a7J~($>VUa z=F%8@fUJzlog}|}4mju91}Kf_HGPC}){fL@Z6Cp9-B}U+-#)^n=M(?=2A>eS9YV5`**9t#uwYhPi2HF;UIsjo2gQj^606?3M7Ia9$L_GD@_)K8EK*Cw{QFNOB{ zKfpPQ$q;4gq_PJiOZ44-!XE30CDEt)3tO!h^^e||Aq=;|B)d057?aV8Vi*|w>{$bM zwD$_*E~409+Mgj@CJ4T$eV{Nx*d84-P{_JyKgx=$`lG{A=%Nb|7znI^qNv$~EV1S> z(1d>VG&iK#$I`A~Ycw(tbs>Fnpl~2ek*ioTtU#hvV#yoBH4e3@Z>)(S2j2qUn$9>{ zwT1$p@iSr9+wp@$Sl*+rR7c`-sS;h}tk3cGUq7elQy*a@1JIp4lkvl3qvQxcMNbbB z+*V(&s5?^_+K=#p-wF$PaODqd#!xLh3Y@Ra6vo>ETa%)H$P~s}51t!s$`r09!VNl4 z7&Vv(M}F-n(Tx@geC$wBZ-D@4H5Qrr=g4=V>+7%zf42*H+BiX81=6- zMCbj_Gz8_ACr{8)5r#{Ia}O0}5#d%26^0KW!bzS6&<;EFK)daGJ88FNsBoSw`GutD zv7w;dy57;0VZx;_zOEc5RQDhY4xyRLtkM0$g!^pbo}_5eaKU|fHc=80g&|hMBlfi$ ze^Shylp(EaiQ3%ELX>uK7%=z?ABw&*9Mbw)^t<80nEVowh4P7tDKp9$nSxbgFTDWy zgg5eQksz;UDYjZ{;Qn%Q>XjG|JsF+vhKgxwOp5-+EsU}CdM+vYnp>FMpU}eh6k(~D zKdz#vz%_d52w|qR#2Q^YLbxR({S$MH6>B~QB8*3LU@g5a>KrKyzeL$Y@k!egk&h=5 z;i^F_w`%UJvi=nMfrvl8$_-BfRvnE`ddH*1BZVu4x1)at_>*A!ygn&< z{}|!b@Du!}q;SwHqJ8mO!Ob{KQF=KU0I_J~Sc$WP50GQuKnCXA_|5qMtC!iXR~*Pd zSQb1z_?0i1ocI+ei;sGQIeDgEYojcTS#*{!e$y#PyNGxmFqg#pV>G{RvP7Fb!nNV! zV)fm~X6xvKx~l*iTags9mA!_znnqddxE6xm)>3lKY;BtZ7Gj+cl+FY+P?b33mV8m*Zxj!JdF<^N;v&BL3jy0&rlJ_lN$kTRD! zK!8dL7AQ~%O425hKmtWTiwL3uGO3jrMFVXa$|20MaaNpA8~~LtD6~*$nP&wQtpggU z0uE>q+kW@jCuvir=Xu`W^T+RczmMypC&@Wy4{NV|ueH}+Ywb#~if4m9@LL0MoaUg5 z2u@gCj`Wv*&$k&wj;;D0*f(IgJOCwe;<8Qmig!D(8ugEpW7!>m#dE)-96ZdXJT`jC zvQ2}<+wEB`?^N6=^kOXd6f|xv(T8G+GXlzFEB>;{ZNSQG$`EZwPC;#DS5ASNksSpw z3HMu4(Q^XT;DW0bcBlI_<$Yzhe!H->wosCUZdJ?pmC3uf=Zl&T@7HSt^u_hj%nr-tRjAxOI*KaCLc4TepEUHfo ze-gH3hi@>>t?Tl%Rn+RlW`{>)(12j;rcyC%vz>E^ch*TtzbL(w;lhL#8Zr3TwjqZ! z(nzp(AC`ka`^m{(Tl<`u*HrrhQ>inz8^dkX@|}YOPp1D z?;Z@Ss!KYnOn8Ump+KhT)Hff56agnfsG%$EqkTQE5eiPAN9|x)F!94`p?8>GqfsE0 z{+-nT_Sv~KOuC*&VGKCPM3MwVf{;)~XC-rcRx%Q&*>^@FR&NTt!>wBi;Y_xX&#$rx z4X9MOmK-!>TW~}r%1$n%=4Eu4${;$xJj(1&wfS+9or8wov#v1rv@SQ`gqyiB)k-B@eh^`scn zjXlux#1|!{cEiS~2HH8p#!jey0afqNGB==Q;)iamvn;YvBD+(57@El9n$*Kf9xaEm ztGgA|ge&yn!`jD7src6uS!SmxpO=&-X_;Bnir&5ig*cu1f+N1MJiHfolMVhs=Tv8_ z9jNnpdT&T~Hd0oR-JSI^(u>YkM{F8gX1{Y*-{DX$6*f-RqL7<@#k=%)^B!y*uX93Z zJy>J;+B-d13;E$dk6>GCTG&RhX+uQR@)3Qbu<3*XCkSs6JW4Kd6)&zB$iq_Ot^ zK+pE)?urKl?!u37T%re|VPG=mkQW_&1;$Y*McGM#$rnrLq`=zbsfV3xuyG*mQVDn$ zPUoSc0;i!gIw}y^kB98&kS2orYV@<+DeaL@{7S<+`GIlgR4c^6sOLpk!`d21x;;Md9v{)mXoh z5nazAfs;+k_sVDgyao@$c@y~}0#0oP)ag@Bb9YL(JHto=98Gf&*BD$ugiBP9Z`Qg< znwfAmk|T&@5ompgMz&2dF{5USr(fi{O0!`pjSYZI3E3{++@&anzRsd+PN@>u_BP-I zM;_$OeHA~^zrYJmmzI9k3ZJsxleN2jqu?JUN`;Z!CRqq@#QTJ0f9m-^ABL;V=;jQo-SdGEfyw zl-UmK7!H)2=JZugK*_W@A+sIW^yCL9Y=q0}8yKp=j&;+wK9`rBx=5p`7xl%fd1OVwQWzJrXo2AP0tWX;a2#bzRUng=>|?X4ifc|0OW{PP}mt%3_Ufa1M2j` zE){&b0!vG)#Kdzn6a|ELvYuf7VRch2;Yg7%(2jfIoCnbYok_Kjjoh2wIdQYjh-Sjwndj zkSifB{ETO@&B6}wBR>dVPi9oSP*m6TWL>Jh-Ao289yW>#J=uQ#+jss??_i6W^6l%D zMZaEb8uu59yk6`bFFcj*wGD>r8P?;$)EV$3&Keri`Aa>Izn-qeu8mRq_|&R!1TD~h zr?mcX>O@xm!<3LY>nQ|2Tp`AqZre<=&5_kN(iOzFx)6{q-$0=80R(FWe*U(kw6LWW zr|!ZEl{EqS8fk86xCO!irh$J40+HVn!+^lh2YT)*DTP>?G7!!HO{PuPj$Lyovt8Sf zY}lTeH0zPb<@>faoNx&$N|ND2`y8lFmuv`8M8@X}k%frWC`R0l&F7#qeWtY|sYoL$ z7kVcGoQ@MBow8*9)S7*L3EQ0tlEqCnr4=H#joUOGm_%p2Tdqv%jKvn8Xr!|~mWWF> z?5E(_Z0dYkh=R7w1Y>r5>plqQ^!qul`v>1vC*qC1I~( z?RV5oMDvx-pIWuAuP2&bS_p3h^w4ejc2XOsubvYH7@9*+;~%tJXmF*uI4bed=twa< z8EN?LbSfpb%{l@slnZ?uPG-2aSF7+gx#^%+qrSO8uk`!DUdhLvLFcJgbcaHl;-O>@ zLB|pd^St;;cJBEdCG|_o&*KhtGaEyXZhFIq{$z0I3nL5L(Xm*ae0N3IXSpGoDeqW7RJ3vy9qKd0@H_ld!A46LCN!*=?sG*%u&>&%&zm*W2!$x#qkL`Gvhs=&KK7H*xAqa-`}5IQg8jZl^%a|5IrvN zcr*XogV<~T;vHGV*dgWbygr1zsW!Z6v&4hLS^Z%7V&!o5F3b1Z9$+q&dbIFib{mTq zr^d6k{CFkt&BLq(+a%aX)*QdK7|A9?5Eaa`^ZhGEGJ8buQ4@824(9S?WP@Ij1N{OU zM5Jw|d6Wy#U;vQ|%1W_M2rI=ufjJ_mrjkj_h|-!*#$}VRPG(K;ZP;X%URL3cli6QY zI#Y^4ZCng2DGtr(&{9>mmc9wKJGF_CA)SM?%K7-sqa|wTC_8#4Ylza?&1Bw4YQD`p zKi|K8CYu(~a2CGW^`S!`h!YB{TiavTUdtD@Ojfr?0d70BzdYI#Ncj0&Hon=kA#z}2 zJ7+uQz`GpE9j#JEz~$4X?67M`?dBusxLqvHWL>={=?x1)LxAu&{nlYf`b$P1I+ZUF z4C7XcWJrxXsfu(o>^^-Oj2^O+e-F0w@n$O7k>hPzghTVHq&-kO(8U5=?3RzEfzB|e zS|qrW^7omKlx90Dhv`jYiUW%k!(Hd*dcp-tXKR3IWxvub-C1 zfo{PAGGtSa1N9r`vg*tVH(DqUpquqBEpC=wGc9pwXWR<83SsY20X-p3scfhDr2c5s z641&_3JBmfR@^s_HDU!~$~g3o6Iz$=61vwgT2v)HO?R=ImY zTSp#nhWIM&5mk%0fYIKziT4>Z=WrHXA#4QU=kARCM z6mCg3sK+=_V&SU2tTGR@MWv|qKufcmgDg+83y1P$yS;`asH|8fD<=^{LAzerrTG~}3er`t&?n_` z9M`b}xfti<8#3uwQw>IH-y=-&9U78y$P|3)Q(7+)b+vj$$Zm-9arRAtPL8c>%5@mnA`UNNriS;*4PcJE714&d z?|@{GgfXx=m{$JD>hpP(M9iPCR~0@aJd0UBrOxOj;?>1$0_IbVC9FZuy@qsJVrvn$ z?o|wPj*ya0E%o?@18zssDxtp$Qt3^$!PX+1j3$mWO&4R9uqLgdw+5T()&j73DAY=L z2ceZtpHs!IY~X@;dkJeWY&%xP)~`rZhVRXtKwbR)Dc`dVIs+?ld?CD;>A_ zV2G))Fx4MYJ9;d;rwt8t_zF7wp|CDx#^^=V?~I&iXU1-$p}D=1czh{4=S>5khd9t_ z!Ld(^9@F# z(Lvy%GGL(7t&Do8`XTIt*s|)TpQA&uom!&`&xaUYJ}(|z4#+54#tcT3Ilz^`4ZH9sl=Fd`>QINij&;LyJH4S!E z`Jt8Aww!e_jD~iM8H1TaI6OksSi#~FEkRD#dHfK1NK6CB&BaH;O|bVPs?7rD&r@XF zL^zA&74i5A7BlGgjb&VN={jCE29DEhMZ<-ZcQFRf2{P!h{2pPfxPr@IDCZ<$XgXgDmb!* z5N3F*!pMYf-G&S-@?O|bgzjO37$Yz?A{>!iN(mSRyeXxeJu7ZD4CMgLBE#J3!1BUY zB+cMESjM_$oX>Lw2mB!v=?XTPsl@UQ5QGj1=3^)WP#rsE5(UZk>2|oXlbIkJGy#HPV?tk*dgA5&0awIi>7?~Z8lst9$X0&fx0+jC2QaPF1ZBHO@yx(Txqd{ zkEQ)Vsec68U3U*)61-Pae6tb`nZNxk{#nUdwKPe6M;ii7=aO5qA#i>@wZWwzAZG^H zE-P2q1am-JohR5Zsek{t3fsr=qUm!iCfaCMPE(y&IM}ROR<>xrhQ)~|o@0w^=Tep1 zLo(Z8ekEVjex9vitHsvm*9?wPONAXnln~L=KV%=(puS6mQT9G*9b3=G zJ}(kig9eS)V@rJeb9#e@t=z!&vSni51~!=}@)26{i9hyZ)}8UI&x(gXVKaI03O~Bv zP!9J$8(F95Y!puVh7|zgg1#>9^kX?=6@&e(V^w1%Y`(eR_9qtmS>qT;tPw?aZ3v9s zI)-2&M=cdQ{A_efYNj1qkmlEjuU%#8o89~z{a~J*>+iFP#{Q(3vzfJJ>%{w;p>?eh zUADj?v`XyS!gli`i$%w6tZo$Gs{_rQ>4Ei&McUV_fj@UEdyiH9*+&ouGl3}0$FlOd z&lsi*jtawqs)k6}#@=L)h(GCDZ&80co52o$Uj${oXvPhcvcd`dt=rsARoF+lt3oAaVbZM?wdx$ml-;>KaGJax?`1vph z^PX703v&a6+0CA%@!ZXF*b=`lkBwz)wD>EZ?O+d!EqgH6z7n>*>{0fC_;4>f%bi~8 zWm5F2)b zp6Wnd)>A)v+KaJ?qVO<_<*p~i2b)=}xZ?=BmpR3=N6^zw!h94xjrad@l#OP*>NHW5 z&)SNa$1%a{h-xR$--Tl73G{ckC_TY0^S~s%zv*9~zig3Md=mZ}2LHj6tR1TtKB_dL zDWMT<+{~pd-30O@OBjW;SPgewL2hh}u-3_`CB=)oPP1m10i#Z{`E@1k=p1cMmt(dd z*U!FW2@Eak`V}+ttB+An8i}XBVvq2mN5zj{v6Z~wQSrbGDqaaQF0+$Bv${32#JqPv+$H5 zVsY_Ti{Rq1*~418YLHNB%{gnK3aT!fem6s z!hDgvjmxo%>@HjyU1G~o!w0x@r+1v2Lb8c8GN%(u9DT(@i*npYZl&NF(TU~$EPOGf zeP=O$Fk@uk1NCM$G?>B_iLF zG#}bw7c=WSUdWQlsjX0#p!hOtb8bfQfSaO%9kqzXC=l^2y%I${c z;?Q4k{wordi(p|Z5^amv0%ID;uSbDjyYiz2S74zyf`^-qyPLG)07lW{bKrnOZ03>6 zpmxTLWuxD41u7BiDDL_jlqm3z`kTGTV#!L4##nVle<>;YJxTNZ0DlabgF=e_-y-fG z*4Eo{?7gL=ncCnJ4XrXBZ`rccrG?dk`MhL)1>h4}KnI1;TACJ5BkX20>V)~2jSjI- zJ_&A8PSW|x;j9CeNC@)IKH?pHv5<|A^cI#1F0IN}0l#>Dgj_dxX`E6PcjAcV>Jh3=HcSFMt}b zl=D_F^nSwmKsHiTRrrx6n@|jRE4UCro*C5V(w>TUXv_1(p9+5$BIY#}ujPteD*uY@ z6RRt8cq{t%Rpw(^BrLbzq0{-IX9QmnZIw|XU|qX**{1ojML`5ls%9ilJS_2jxgsu- zcV_$jBO>{m3_4hqYAEVXaeFnM%>TCfCs*Uk&;iOFE*eMi7PQ#8Gm1B3CNV6E--gRP z{jxrakEff>YRH>+)Zk{^9IIcRt-+^--E3b|-W;M|=G5d>7Q4l0>3-47fY#PgJTT7DsbaV-NhH9EkjKkR`>kdr}R= zK)2EMmhQ;7BKAk~=Ip5WIhwbsL4H!5zx4Q`-i0khG^@qCqM?Ip@eV32v&34BH59Mb z;_u+sKDGHBR8>}OZjK2bWrVLGqpbG1O3vV#NNs+;ppYOyTt;merG3 zkEf%at@Zf*Y_N!_&%467Y!pwRv^H;GM!K~j*L2OLjf+Izi1HU@4_~OyyIfmy)36?z zLcJ@mwQd9cIBLyoz?0dVBEJFeTp4zF?ZCAQ)I1dw(5uzvKJV5wG zMSehzyuV>ts*0bn@nqeKc2=Uv^J)yQ=gr0!9Ju8$U!}P3K20bv-@KRh#-~%SDSdM) zDUNhvD3RQyz&`<-Fc1$oI2ey5kl`{98v=WffKSc^IdUi#woBS}rPjgM-(e}T8ScVR zp9LAp`s;*BfT%2*rc~^hQ0L)iq7N;8O!Ot+0o`qc17SiL8F-T9PAXPeWH)r$&8Ot9 z!_0=fW6c#=>SCCkR@lg(nkTk41|MkHmaSG;!`=6s5Y9CfD?bnAEj+aV4h{?R5Au*f6{!&sqD_k;jXMG6CA20 zTV&axieL?~PK^cTrr_aLkkvq6t>=831&*8tvH1X<$Ck0H6o&@S$)B=>0!qANWH6mu zi*V!2Fi1K5C?jkn+y=tiwR>T$e876_J1yV5m&V0vfe~eeZd3sQb@h&tZl{9K2WhL! zY4<%;N@}lDt1q=x^J1brWXxfw)>{f;4aU!rByW2z5v?2ZI#q{&O-4)0X0OJ)W7U6d zr?n=E5;Gd}CZoRyeRV4Zu8dsOZTWucZMdD_(!MgIl_Wg^sKRE>BPVURm-^HM@;1xN zh5^Tp0ZSw*S?yTw>%&1q30kEX=Dv(4Kv|oXE3P)?t-Md*dpN_QSVZ1>wEH5pV;=2E zP~>|#uP)HM!cExMkA3IR4iFoIhlzuTkJCVi5!bNqbm*Yw93o;O5pe=Y29tb69_E0b z7n<;9{mYp38eR=!(FC1EQyi9ElO_>+f}khCmI>fa4-~aBWgF^_XwsA?4+yadQG;@# zq!2%jGhv`Aq$NFK*qBc#?w~Fa*Yp(NA8-yleI?^eh7R_rB=`~$--y?X^0(Jnv_4GE zVn8F_Dne(%5u$oC-T@9K|1;jwm$AfKNVQhRTjPkg;IMEj-hv83yydVU`{Qo0wHdEb z?L1&fSNp`FX1u3kc3hCfVAps0qHxl9{uJmuuzTosefV+i3fy1zkP>iZ>;mHzO2$id zK}iMUiInENQ7Z?EmTJ#+OqN1SmSC9MO8A=dIPafa2kQ_R>bymA=or{1#;#pO*T7*H zB#+QYgNsP;K>vadA2DbS!w}A1)lGPvK|h5fpmYkc7_{B`+g9FFbmlkBna2Pm=R|Hm22XvN0x*P0!1QF7yVDB>=BL}ww z19s9u5@8(Ifn>K(&64qH;!ib!{jK8B7QBHsFNlZB2oKYOaM-|&xI!!>LvWaVn}oyu zVQ>hO6y%TuMFKYB(U9oT`-726)w?MXP=X^x-KOX>E7(!5gym3Ab?4gNFW^U z&Zndj)v%S`PsI@_(o$yhz+D<#8cDrJbIX zM1dZuQ`>wIjb_s?POG%G* zE~Fb=LgqTD8)RdvRO$vfYPfFj4t^#xmQ-+qI^zi`=i#S{SwX9ap!uLbCdWQh+|z6O z6tfKm>3_%)I3FKF(*#2hnkHlHX6f=e3%9k!+4PW>LlfnQ)*m2!lynH7C2dv8oVU13 z_Qe!n@sSL#kyPs=+Tzf(2}(PuP2_t?oj7(D-A~<|go2So%q$fP?Ah+5Z4V@;!OyT+ zHj!8A&UCVE5t}$|h8XN160i9BSv5fzFQ+y2!LIc-p%L*8OO7F99u z-F%vyiAH!m%3-*OVxK!m8kbhvWe=QiDeCd8Zf+OzsYMkr3~zw`<2U>iO}BE5jzKht{7 zwh_<<^$YlqlXfxLi(nRIYQ+7;JqS|8$+-G6D+y@KdxYhs-WYp4yvgE?H z;Bjl2>XIf^m-3>^G?!(vrJ+thK6tRr;?Zh>3NCFxJd8Yw-P9ALPsk`nWLGY+tJRRS zizXi_mRM9r)sg$P5y6=zF0|tf`b^i{=}}~@MAoZFjMaRbRzhYcQX1^tCnJH%+^_JF z-XxLllUUsR&YvUf`Jkf*QH z*BAT8a5>l=Xj%50uHbkqKTV}=vP6&ej~Ttd|DMonTv-lsq;KN1>^5Y2VGJap4_6zE z8fi<`pwA8MxR>G(!TTRI(fNJUcn~+3ZB`voL>NRpF`$b z&KY{2Bme&vDBFWoMUqc#M{zqzphcz99hMUleay&u z(*pdP0_&0P*&Np2`t(qFSKFgKWnB%1lm%6~GJQ_(^msrF;$52(juAB7VJ% z$Eeg~5!af>v?iy{>p)_91(5itg&5zOH@yK$B>ZP6;XyJ;k5&f&;X$rS9Ux%RooN~Y zgrMODN=Sf!MigXg0t+i_poY<-E1<-XF*eOT8!(X^1{0s-;l%Dbk{Rayf}RDJe&Jpr zv|vt-*VKq`(7@5@!5Oqv&Y-GK8FkRGig(+92?mC~hD{j4hWJ}x!#bG0g7uf7LkdkY z+2hh|Br;XehD2JajDWmJ?*L>V!cbT&xXuj4AEpue>?lq2ZOfw?(KHWYLtm7QN39{? ze5@^R?X_y^CW#0DDjg1p38@;SR`gIgY?M>Mdt-=9hq@8ge;o<|7Bo4mrFlVYl+e)~1UPp$p1)2Z zaqBw5jge!zjvNn3qYyC+!AUuE=o3_;2bA^-&~ZwtBB#q#5r77u;#5%mI2ERTyeL6d zv}n&8NvBWLkSd;72r~*SBD%&Nh7X#)*ai<1@yf6P$}X^B(nL}wrf)Oi76qEpBj#Tn z+DWJsZskSOEPP*L8(W8zagT2(X%!Ue6w)Xl3YJNgf}Ct@rCzC1AVZFh2H?kXMFNx{ z4?N-<@`VkR%v{@Li!%QGzVLNWR(*w69!hk-&fSphhAPaAe0Wcd71_a%@pd~aOOmjc!nzi=~Z_8UL zNmjdP9nYJOmwu_c15coZv>1bYdcoH zIG=1Hu@vv{fg5hLERVHY@+NsFQ*eRPq5W<*|4K3F(&!Vk$zg$>qHC(PJU*AcLV!=^ zho58CQ7{Wu2=-?Y0%)i6W$06BHp-j`384E&ZKH)2E#YYr(~6NAi_wgQdC3MVCRPOE z+d90V7c)I!w!`wPP1{dWw0zZW$ovH~$Hcb7>o#s;Ycj9EZ}%{k1kDoFPKG(}qeN-u zL{xeVv3{U%3{A*X>^JH2Lc+<&Z84FU=pz$fbj4l`nIncsG}D^ zwj5GC6Q?ww>PG0+9Qexa3jErD#76+-8+BvOg{iH`bVy8wQ8OdQ*=1ed?Bi3-(tg+h z?GJUGpwmh{!789Wen+bks4Q$l?*=i^b%!?DS4k&?G_;m2qpxB|?cDc4>2e+aJsD}& z3O(Ayc)R%`_z=cKuwu02D5?4pBhtL2UyMPUVjS5IW0C(Z!a1Cn6`wG|(jo~95b>{7 za%0de`7bO(Fqmh92;fAAB>;6aoD;?E2|UJYDl;-VZQ51hxZ?`*XXl4B(?8U%;F7Tq=R^IP&6aRH4xXmSb$B5XwLN#Knic4!H6C4nol z*~4H}T5E;;~kj%qj0%$0i=LNt|hg=c=4v-1?zNt1pu=W1B?g(7%gXTNY;%2 zEQDXFx56*)H8$!pgJL<`+!VlKsS&hW29Si0OWvgWX!&NfNfm@ZD*z@c3pN{DOV>Ok zT9F4iA=Lq|oL%bjkm|8Kh*Z!#DuNTC5~iW9TZGgNeZv8vq6y1E;R@(fjA1Y18HrBT zaCCYn$TwbXU*O?7VemAfjB&_L^Dp2@N2gZ$``4forfpEH?+P$_q9QuMR6yu7{3h@; zIVr?l1Wm9tBf4U02ap#;I8Ml<15*W5LPBNYteiPW2(CcHBO!)(P2o+&lApo#f2`qC zu(Lhx9p0E3#oTxJA|go4 zKwj&x|ChDc3svSKqmS!-zFFym50yXQ9hB#yO8s3wzz+E}rN>m4r5jnW@TaQD!u#R7 zEL&8SWe;MZqDMyri*APxvgjFAO8xhI%-yV6HU@+C6LVnfMpzXIUz@fQaF&mgD$tFd{`H%=a_b7cMsaitpQ2Q*R4V#xMTN(9`^RtPRasSe z|AOBp-u#q%d2ESj^BFhur;7apKjYOH+vHz#iq~fBs(4~MuUGX>-RWlgRk8gvHv2yl zJGb*ql`+ufe6j9JKC0>86etb<={#svdU%2pNq=B!+ZAyCEjoRLVcjaG=kT=POS7_h zD`7put?<|xu!A?K0oN-SqaY@C2Tm1U6z*L*yy@+Xz;7HLO$oEZ8mH;DI+ z@@C@rH~aqqns-iz1x^}3FnyCkOU;1BabpZQPk;Nw`tstY&l${V@- zdEV?og;rL&AcpVaJ$ceEV&MV49S$DugZv+^{w(?&;*BEiq#S(T7mD$RcmjWVlX&eA z-`OG{GY5o5L8>1J62bCUIpooD1AESkIfwZZyp7*qbeKVPoO=gkMjUua8`e{4KYY!V8U7P-3i{F z*Vy2%^*OJ{_*>tL*e`gYu{*-4DIKfMAoWT8kA)2sS?uVw#@~z1C%LU^>UWYph|x-U z3VxL-8E_)paFTynHk9udgtgQeSX`hFqE&;R~X9NlVL;o`bm8#-~Uh_$~8z~Q{MdRhBE65 zeJJnwAUKqO*yiC^U?e{yw#(4W}<-Nkkq3n^2Yw%An;E z-NjZn(yb!Y44f`k?U2i;Qwv>_Vr^gXSf zqmH_LZn(SVVK=RG&+`^MhGdH_s15lWNW>zaHFgIEK}nU{sBR>^MyftT`W^ViGv|lN zGvr9YK{ZIV1b7KxhFWdrT&R>VD;ttPL6_yi)b3K*ukUZ)wSz0{4A``Ti7 z8^s7$;cPU==ChkhU;?7gL}jt4jnb&z2tAo9LyCjwsl%OG-x%cV72jUs_3K+Hx(v>U z#tP<2@l6}0tvYpINh!%Hv*^-RY41hegM?nAv&L5O_y!}5KyqZHOS|B){AQR(&ZX2r z0M;(r)gi(l&q9MY_>7zs=di_X+4-~i&BZUQxO-sb`8pDMc z#XY5aGOj4d8#d>#Au|GXYZ2WY$lUY~)rIg!-zXR!FS}%*)?|}DvX1<{;S*aQSxCyB zG_vo}JuEzR8lrmz7zDUz=BY$0qGOPxqIz$o3IA!2SQ4u^yn~C}sK#>MFn1-Ec|igM zd*2z~_1=RX*=U%{=YWfIz>t3aDm@!aL57VR;&PAqf-S2S(!|=bFvBu(M}!O>!*PbH zEEw9bvvils9E_McSNQCHakUDM6_?iVDqfnnR3>?B;c;^rg7ciG$)WR39l@imFZ+@|Vjn4OkW*vZ5f_}7%rY3t}EG~gP}$Ho%RX8z5teeT9$)`*Q+ zycGu*ZjV;%uwWZvhn;dt(@air`^K|!9VY{j}Hd@?5@Se$0_e#stt%h_Y{6mrjX zRGRWAigkfTzK1I#h7Tnp%pVLQ;t%oq-u;2r`DM(3wk4M7QmNn&;u@Y& zs!n>8(n*x=bYVFuy>3sUs(9OHC>}ElgD7hRD-nr96-yhM)AFKe7M?PsF9j1)R<}#| z=D~bx;L>ukrm4a@L85^M+r`Kjg|(ud7v$pz@^6V3zN?*QDd3O6Xw#E`sKJTq8n zn39K6#$A@rr+o|ix}0(VD_?JdeVscXpk2Oc2slQ(w06d2{@UZauYlYTjQxCSXOCs) zwBcm};cE2M2SwWsN(6xSQ5OMNa|O-W@rkz@o0s*fo(@F$YN=wgECOgKQcYp|1tWCRZAdQT33%jYfj zSbm>YHAv*bnjk3>;t*9kqlhwvzN=&fm-5YMWFmG07xm-^!TCJ*1rjPjI-lG6cWG%b z6`r_K1s(=C*oVX{ZRSvNI~*?HQI6oWc>q5_H0TWr$`S2O7xEU6?V2<{Fndy@UAr%a zpc^25zbUpqBHQsRoO__ik+=ZlsfenB%bAwc#HD20wY#A6>8Y?hmT$?+3^(Jw5CuSe zqr?xdxCXWw_fQM10+xHYQx%~!S5KVXS znCiiiKNQ%JEtV)sBX4TpjcgiZgzwTUq9Bb>^1xu6f=`!>#7@WrISYLaAxERP2ng_{YF6)fO#rDWw0@YDx)yEMQRy{dyIZNFz@tl2weK7p!%&Vn z*HLve-KH2E(2ZyrWJjbBGJJ-PO46cAa%)Qyk91MudxYqUAA+(?DkLP^URkZnHr&XD z#HYm?kuY=>-(KVm#MfPv&i73No|E(k!>yx|}dp5z)D=~Yp(lXgd7@{v$r09fKvJ1)K%S6^`mv2%5omoijgVd_y zMuLpN091~HRB)_U^wd=G^wG)|Ts^kPbw+4g}Swj}%(aSjp2j-fqr=%mR)%%m*A6 zc#rD~PHb+47MyWXMUATo@A30OfJi5!`v68Ow#E{cod6YB~8l+hV;`iK?B-D?bG!Wea%Bk5s3z4ULvTm|pa?t-k)@_;bzgyNd-$)2j!J->nYDigEiEjTlWnJX-{~Ki8 z;E!)f)vk2=za{H>P5oal>*B0R)hg?Wr2p$gUFG!^MBPv8h%^2_h`Kf?;Q#NU?v+%f z0l({n*p#Zg#EkxXhA0CW|Gt@j?S0C7Rh2iNDi-&RRNTssb*qT%p;elQ>`{s;_KZ|I zRC%^;l}cL@#I~VTnu{h#4zCo}(Vy|-na<3Zb$@Yb#+6=EYv42o*iaq$898+dt7|z) zhK&njDvgg6bskYnO7_@D(c=*%R(=@uh%&6%mZ5m>s(f!}*?U##Ers4HctlB2W{ry! zoyRCW46!&`#f{V#PCCD0NTQfIMtM9EQXk>Vh-m(OjPgpYpa&K<_Z`|#u=(eT*T*Vv z;xr-GIOQ$=Qi=F=oMNJDgYilduKuCpl`Mrnks{7LrX<=ZMd#*H1Dx7(3`zN&kJ>V} z9mY?{R);fVk*sy)w#tA|WI0aSw7X1M4%m6`!qV5V#-+_Is3abGT)Ct6tYSKV zVGmMY;}9AOwA?P%Kd!Wlo6ts{*XD=~3qNth)6sN^S&o0mcsU&V>rn7ft0;b4iEZ>i zO)_!*W;6e3`FfJWt-WuA{-xXG?@Uwd@LkDBQ`$E!YqtlwTD%3O2AsKoMd(Z{h4!T> zEoD+F_+KAGL-VEQ%tBKxI_C1JOSHsNqzQ#VU(|X+83$6$d_wt%uPhNACn~4;vn8U& zB<1&ZCKBNz&P{~(z6}rGs={`Ym4sILC>?J9(pF2JIyb6s_GIM|)E6~fc~?2NtVC>{ zt_%|Arzq(hUlz|$V#MZMia~6gp(Kg}Qt62jW9YOx6@N|VMVlNC@s4Li7vT6VF?W`(!XIZDy>HQo82RRYgHy^;%al^J>^jm5SF zN|abUPr0AZDiOcTQx0-(iTG^3^7l2>on5FHt9xDzj%-%GxR|9hGTy8|;Z-#hNeh$_ zVf8)EpDq!%YswdVW{HSgs2sk&Dl^gt)bjm174-Ef(@^$KpR$TSSt5olQoiIfN<@#v z7}4n^{>n@A5sh4`Jj9fTUaTw%)~faW8OxN5oKGwflb==cktgepm5QBL|K6XmQi)^r zPV}v!pWiblfKfs=F1K%NipUd6Z%X_dy-G1vlSl**df{1d=PD(pA;1UT!Vc_^Iu7GX zAhZf6l>vkvU!}O}KXRPZ6Gy6TGe?Oq4Z4snd$l;YN@>>Mv`Gh|M9pC(_({paq1VkT zt$a>tS_KHS75vufIb{O)ZOL;=T%9m}O9r}Nv(_pOJ*Tv9OAMzQ;4s(SS)?KTQ%k#=4knO|gm0A2zz{?9D*Mt)B&kM@TDyDM<2(1ybS1WbI(ifFcH>|Si zOaEDw3-#I>i$7mgMko!&m53p)DQ@xc%gQu9wnQ{rtqcZCP|L$IIC#-0S2q~TBSj(?Ea^aI0WVHtVeNL8K8VYXn5^fJc*9`Emnet&>Qy}paS<8Hc4Rol~p8blLU9CzZVce#4jHKx);o|;n zN_$F4qHR-JV}#dlQ|ebOpjUh~sy;3bZ&P~ItM>s;kd}x*2XpA4FhDflt~71%a%&wF zjKCBEh4?H-0YE{dY*%7xm|%_fXphY9StUIsN745T? z>Y#r2Y^8a0i2Az90bF2(kdpJVmGPj|(7j4S5tXAn&U<4}b08(}DiQs6C|*=_Xor${ z>x$xKQk8Ix<9ZZAZQaBlJE8QJ*H%@0P^ehMo4Js_$tB`QE`}+oM7*|3*%LnKI_#lO zPw!S@ThhEB30kIN+@!9O-PhMuO+1sQ4C7W=mq)bAS2B1nSz(OG%U9Oltk!1YojpqR zP}QW}=*q_pt8OkX?@@@OLDa?kjuP?nUgZ$)2`Re|{j-z^-+pBd0HV$Tr9S7cKj1eX zQVJ@l8}*JTkAd)+N0g4?gtq}CkRJiN1|Ebvh=WIzP9ar~_FpJ@R#4?2?IQ>fv$I93 zqslTgXX8<2Cht-r5{@ayc|wV(c3e4n>*5=Ts1r)#f2)o@CuALGPAJdtP7uqVD`$Ae z64CPuRiCfG8Rimk=qu#}Z&xDTI-~5s)j#fQ#Xt(*-2ZPDNZ=j0* zCn<>t!Azs~INlzobkEXT)&u z`VV2m@vDRp)YVYr{;Evn^)aD-Q}*$C5=+{O_}`UxcwGr7eMIX&ly2b6VSgx5P&*R+ zku~L#A?Yym)m2%8Q#v2b*c`N&gpeafvjI2;;b`ojDstW^E=H-zwSRda z%t}IO`{RVYhT5XhfbO~;GDdUIarTbsS&p6|(5Rs{jv`eA`QRco)FfW7p^gM*MAlT} z+JFwaYTyrb)m{9zU;}s8Lj!*ohWhFb_Ny4tL~SSv>#G;b8#$I;^1EVGm4$;xh>~LV z5Vb)kJ=t28XI*en?jwR=O;qr&Nm);vYp8ZbbB&GEx}ay5Mrxuww+1E(F}acYG5;ml zP)B1l^cSLJKk@WE>TRNO6ZJm!^V?{0h@_*Mst>b5|DL9|YHjCWYeT7WZ01mN^@YsM zMm|O)HCOM|3EQ+X+<@oE(QnYOK${<8fI%j^%;uBj`IHF#b8|JH{Ukmys^eHd7+a`c zvLD4iE!13g)}PZ-Z5?WNVJme4`$43d)b9b5JzJ}tI3FJ2Piw3GEY+`vVC|tb+UVQV z@nGwtx2YX(595l?dt8-h@*pTi;Y)TmbYzpyc&z8 z!^L>DA<|ja9Iw_F%{!_uv(Lr0j_O0~3(=yJ`UN}TztTyK4{uSY1fb|~|MCR2GQ00q z)YDTb1qrCTD`t6C1X)|A-T`aRx+x{m+U~;KRUJ~^z*( zm)bqN*?p|)7>v+IR`qMJ^D-N{$iFHS->g*|_^T$Xe{zm50axvZ?HTV zJf*kVxcP)1)k;|oxDM~KQHCPN{jjBzIV%VjEq4@wzR|w16j^g8->LZvxE1dsxrIzg_Bw{4)t_EyaO85GUJ&p|84=e_HH6-dAlBf`!X&btK;^ zg~LDp1-XD`K6d$^6T|AOk>W~oYDUC|ICryGsU0aekI&j*D`g?fY4kNAdSv3D@Y^HH&gzehd9*B1+RuUcAF z%2qd)BI05zHAb{Zg?wFC>>rRk5!NpQyx}_qOc2|5EF69xtHy^5 zhxX&bg~Q@BwShPjuhwi9sY_)DuoSfOJv+?c7y>DsX(B>ye8^uc7Td?G5Av6aMav25 z=lq3Y|KAhT+r!)1=`nQ#f40AvDs35+&dM}vb0z)dJT5x%5Y44JNO;ERj>HD&_PVYz7g zo?5m;TY$45AXN}dFL!8P#jM5V$dTDMF40~-cVY|=Wlmz=P0Q!X0#FD8rj=|J?e zqJO&DycP);ED44|uI#jFs7TCASD(au`zu|IYZ6B4kdiF3Z+d8r(JKSRz)zv(sSQPuQaM+cJ$&+3zo|~hl(vQE-Q9I!3Z#7qqX1A`T|2%+4 zzIbNdb+z;tzt01I?-!`bDaOwS4|#+2ot=;Ro+%cavrr#?tg`_1;d}Q*>Qu3LfjT|0 ztYagCm1IbHq!f6YkXx^IqyGyh10JA*mRvsqKO*lq0ggFKa3-oSQ6ES0Msf z@9&X?>S_LDv3TF7-bX)HU4;5^Mg2eDw7V{CidEPatGA)M;}@$fB1vnMRjgPntEjz1 zeVU#2#vn{mt3{usWQ%hb%)Q;HF5ce1cEoHpQ9 zFlD|B$#B!byyWiatJ^HlXlk*)^>Vd7S=)N9P>(XcZIwTIrTRk!2rd!OflT6&Rcc3b z8R3w^Ca9{*Nd+|>bgQVQ?_8yJk*v}As8r1ObLxj6)rRNPnfx)xy64q_bnU)Jy+?fc zy!sZOP%JWDP+RjyfdMaIb=l2yZbJ>?PaNMFbX#y}Ps;J?6G;ZzBz>@Gg^=Z0Q@%hb zeNl~*N4w}X<7L_vK1-QFY^69x+7V4jh=ltk=-T^5;Y;dbKCW2Id|Ca8k1ZCXUQzF% z>u0a1@woagzM{reVABdcr#_2ruc5j5;?g0tet3_g4@0gldQE)}wZyJgpW>rrx9f|o ztJORBXo$bn>J>h+SX_P`-5yo!fB&c&!9=S!)do#W=d6`0_Pw0s;+{9QdBu=UeJR{$R04IiWtF)OrYf^R{}4k0=(#HEJumI@Vw*Xc8~2QCIQ@ ziba=qR1>cLq3>XRgD;+b7i$fEG2IWHH$&m6#r}~Wsz%1Kd7HIPO-Q1l!ATrRhou$7 z6x^>xRFA`$HP~2YF0pIhp=GZIjU+b1v$?j6Tm^V`dZs8{r}nOQYHLaPzD;m5x1Jcf zUTx9ftGJ4rxhM!>o0Y_p^=kbv3oC8vezYE~HHo?#D#BM^$*aln!AXv74#1a@BUj)v z8`L&s_=OFd4>qVTfof@fHBLL~)Nkd#K>GPkI)W$cK$Hf~X0MK$+sRcCQ`5Uc*!3MH;DnEg{= z6@F~^8S27!)b-O%d*sq4Nvvuv#&1)rf$^tqD+4Ul^U5~$1(g=yB7O;s8;@*PAL334 zT)jo@Y_$=0N%Z=X_Xcp~0Jv~7E=LEh901o{<-nDH6X5C%;MxJ{0S4{5Q(exuGS0s* z7mGCVC<)mZKH3c%ib*ucQ#*DkLy+=WbTi;W!?FD%qX7=AQq5%)glSf9m?vw1!}GPk5TL5K8ae3_N!UkB2nue@%RCC z1@BQTDjx)N;X3RfpsPo*fAK-J&A))IBZqDVUF*RsI}QW2x&={d@DZREz5ulbi*rZR z7kC$_KS$B!t`fQ$RaiWQP*R*e_OHPz{&)zi8j3HEtCM(ViC81(3cwm7s(-F7;~gbx zwN#$!1a<0j^==y6FVq)#e6jfC3&2*#V*i(4NZ1my-c{TA+n!QuFurP#zw2qzwc0`V zJpU`CX-AY4sC}7H^!ZkO+Z#{rXwk0d z&VR~tPjD&?j8XZc4U5FD}s+=Q4; z)qI2^Y*yhLg+nhiy@*e^y@YOCc1)^_*#AO~NC*TNQ#dgt+I1qb!fT2}0~~X{oZQZh z7-Ty_HxVwWeCNiy*?8Bb;9!9_WVAR19KVX63Y$^T2ihV2;KoC74 zq|1_RNUx31EA%^hixrRAEjuKLUgGrW zl_Ow@+Ccv(sU@i4@r%r zq>H)iA933cYBR46;qk%=m>(d5y}QPoso- zXe7(iL}Dl?tu>vLO+5_JC3Jdt=RN3d3UwoZSh(_oGDr=CBYu?Eg+s^>RO+0q4LT4- z(O{bW0ODAX=6b?`XmX&-l)JgeJ%RHqDD-s<1x2ReKjLESCbvwlh7Z&6%S^YWbW#s$ z0g(V+tdyUOsDg-Px8{jAMAzw!lwy&F=)Gl~_EN7kkMZ1ZU^w)FW{T6-WEU=#oXQy| zrpp@oXel&gJda1Z4^|CxXrAHxI|Pn74RhbemyA#4q{7TaTIUixZ^@K%OP(}~*?Jif zdoRk_0R9Qy!#W7@Kw`&PwOOy!mc>{nT|#z6mu2Uax-xYH;#Cn%b0W}_?jep5xuFsb z>69e_$2?m~r*@D$lw1l%6~-S`bIT-`<&VjC;8*gAHScgER2|u2^y%n)7dNlQRC#FJhI3(3Q z2pPsq9OKq-_B_Uf4xG}<)(00OJc8n{@Br@Oe>@#XhddR#;988%5KYtDP^|l$Jvft^ zsEqfgqk}kHKF>kP7a5Q!hVqvCqV7h{#`#XmK%*h^UOW;SWyu&j99hhq*Vosxxh>14 zCi~PzbUd&dU*Je{)LEJwL&Hzus?$R(L1{^dp@|`CfiZNRI2{>ej4}Fj0AnC^zpW(O znWY|)2@o(*jvOs0$+xx4PM)vUDtw3n`rH)APdrCpMhpkuL&eCk3!aOnDyoI^99Yh! z^)<@&5Wctaf>bGzI21EWqzR%6%^iylA%^sJ2;~b+E2>CNWkrVzowDU1Gzn=D!^#Oo zU!L1Y5R;d^1<+Vn#clq}r6HB>8}%fZtSbz=BA zwG&R(TzXDzFperWWRAu2Zf$Ce+cMQ?$QX)mhUn@}A9AWigBE)cn(fBl*;nYhq%00& z%OCADJcE70bBK0ynlGC3>2{?8!`!EEv1v|3nSNn655&kFbC^#Y`%W}Dug2GKYRSeq zMz9gW1Uy=d|q-a{#LS^`vR=`#)@U<)yEU*hO0D}V%X%|ot;PZ+oam< zw&gGAPh+!TNJLbW&Z(%J5j@{EM=;}J4`PBTRJ!IBN5%}0&PIL9W}%eca(kSw8Cdw8B)9#vdgShM{5vyBNs02N~C z1+|{nsa*B=JOys;Bo5$0G^mat7~Cvpq!yWlW7h#Z=6&`ou=+hQF3Xi^qYTl^P>Z_| z5^9*c8MPXsZHNUJ5bf0ZK?otYjW79<+o$3o#Elk38x~2Z#tj^%5uT&~p`}hSxC5Lk zdtOvOgRT4Ti>j&7B6O3+nxO3eu=XxsQB~33@XVRn2UK(h6~rs3s34}Ol^~*w&gdB( zlv0!OT3*63GXb^K1{@Hl<9JDP+f}<;T3VXpB@5I9@1&KOUC3^SDAO*AQqKEZ`^=!# z|9!vr`M!_m8O}NTy7t;@uf6u#Yp<<07xPYha;T|8{|`;QJoTSVUDp~-P5PDaPl(6i z%m&2s(w@dxe(_$XeaV1+QHsyyoBNj5f~Ou0 zM7_iRxMY5#c@xA1%F`Am&)p@0!(QTBe>0~_rF;2LznLA<*cW;4-^~*w%Zq&F@8&HQ z=SCCCI;waVCIq6!Y~;iLFgv3!Z7>J0ztPxBy2-Nj=s zo9~TX^xBQ~r0fc{CvUh8^b35$Wpghn=`UV+*?a;QCT_R_1(_9hOqyeR#liX*>t+yc z{u!JzIcbSpnv9^|ELKOr61gq|@0Q3HvP4ki(kZ-s9rh%j=Kbo-x$e)OMRR@T@kH5W z)woH)9*=f^fl!xsXEj>t^1VjXiZ2Zh#m7eGV-R_FUc?ur4yS)0Db^3+POmxM26rJb z)GhSVj$RaXMIAjbMw_UPQK?Ib#&RD5d@Gx7#4EPKGFMuxC98P)RdXk6)_c_Y65=07 zQnHaheAV2pd5Q?FP`pb;S?bbo{`^&Q%)biQf7RU8TE6371=L+Nw|9$d-lgGyi00Iz z+Vdir4q7lA18CywV&d$BWWvKsB3>~2_dTV(^%51kf(ihMBH%d$z({?*-MiC3sUQFk z_AGWA^9P`qhXt=VJ<>P8q@C{iR{)-RMol5%LWSF#V_L@_0s(vK{N((`_M{K0(Nu?e zC0LF)i;SuDe!RHit?12-q6b%OqlYyNxY>vd{MT#d@z%(trFaKch;!oj4WCqR?lVw# zg^@9KGjV8Mjq8;d#olEtckCxGnV$ElmM^=2gw zJM1D!$&YOa~t=4>rIB*Zh}b1;niZrwG6(@0(9Jn3`?Q| zFOiw6w01xMNn+|W(8i=}&(?A(%Rmz8k zm(n6ADhDC%nr@5+2V4ONvUmtte#!5IhAM;xElL1upn2>gy})1cT38~7PO+!HO8Cs{ z=1QGj8LA_&?%#DV)KT-k8FO*!Ym{CkTcj5h)Mc|c4tgEq$Ixo+BEIcjQJ~#BeG&Lz zUM`w-tKx&CqdHt@W2W7uevpN9mPB|mL3<%aV&LXzd5*up!r`#2ZkeT|C)I>4c+iI< zncs5V+}8c|(>LOmZUYE4e_+X>iZKj=Zv=3t*jkX~3(F8)T#AOnbm`z8TAJ8k?xaQZ zy-`HTPK5pkwtoY4#2(6F0aQiE;!=k$5J3HX-Q3CD>n5N|oT9{~1*qJmKAw%XmZ~oh zvLJ?wf-P1egv3m=-C+eT8oD|z5H;~B6NXJm4u)3Rf!u@}ODUUJ>B9Xd_VdpKqHei4 zoqO(iOR+9mW8cn^`a0ESDJsN&;_ul`-%7zV)dSjR9f|VM4VgZ{(J`t(*e@0#TJ~0S$N_?49*4v%d;Sb70Kb#LhD{rAFu^PW2s?*sF_rPdNL3-N94|oAs&6Z%`?!LpX^9| zjo5fT2Dbx@eW|qxG5i$?c(led8P81bYccvG4i7NBT~B?)di42ad8qf$dF1B|_O>ZV zVE1nPF7oCtE%e1Z8(F6=qF-!#4Tf+ut1u70D^%ct=NMTxcP3Rq zqY<;dpxieJV8{jNB5EZ`$=rB%#{5moPOt-8W<;a1k_7p6}b6dQ*R8b|o>x)X$ z_EANPid^anqGSwF`iA3VySi>Y0xTO!ZB6!Diq;x;u;EZYm7Us3l>m1gQ@Np!(17Ef9F7zWUKWGTk^1lvG1yrn4Fyy&FR3y@+ zZXgPsf2VJV5w6M-yrK^SQP)>f%|YU+!3U-Wq(jhpH=ao)oABRT^1k@L3;zRApWK|~ zBz;gFhs4X`O7u0j^duNwd>}C7)zt(Z_$vUgM_TRP4~h82J<}yb_kez724w}KZDeosVK$FttZmqDmGuG7>)G7K*ewvz z9%igV#7Q11*7pW~8WE8LR}#(ve>%Ms9?0Qm7)xks(94C6Qae@4lV#SXF?bc&bh0+- zB7JvqKT%6d)h$9#CB)s*e7I}+sy12_m$yQc>+0+=@PT=CZ9*S21_S)ood8yvuEUp-Iw}Ti z$%+CjYs-|>VR3|+IC$V33}rmn@`K5cjYzC3 zUuxxVgtOQ?%+x9B*|frND#5c8aSGOO$wba6H@RF>_fi$`UKy@*#?*(^&de(W0sNgo zFjonY(ACZ?&*7~bvEI^?Dt9$v?W5KpM)go7fl{w{rg_5o3%RyB%||Z8?%o0xI_g)&LwpRW|6~rEot~ShQ~ClQUhmdnbu7t zQWdm7oBTSBNR`Nd9BEAo&AeA1_u9wUw`E&06tWny@C;%9kY&)s0xLG+{lZ zCtv0Zny^$$6X2}4)KXjv(18$M+Jwb4rIK7Y#U(Juzi+}ig*WxyKvL%cpFti+q^OGS z+_3B`*VVap@5|KRz%mY)<9xrOif#y$iHk+9C^N6;@YxZpL$k_NHx^;bU*gY1u=L)y zEF^awT6bHSKjZSxI-2fEyFuO~=NRwx#uf8^m!S%2dmKBOt@*gByGed8U1 zi4`lyz9fXBqmXPPU)+?nHw}*ei$C9#-QC{@F%qU8I6Ao{o(3fG#!gB)saSFwL*~q) zw^}QO7&4$k79oZV&xcl?+>FI0TG2i*C`Dbm5h;jkDe6k<9ZTe}1dA7b!kCf+(4c#;iQjktM7#Ud*+%D2U$Al)Ah^;38qazoYn+rDz9VXJyHD z8aSTECm8bXah6?x@7I%{s6!}O16za+8H5Cvw4Sn-%IWIev4p??mZv2b5v%N)I_*{e z7w@+{$*qwrs-+l$US5YNc#O?$j2bI>Y9#A2aV$t;PfKL3p*L~6&t!BRriXbRlFhIs zauiZfxYKmdX~G`L5HR;V#CSd=5yg<#SRDm7YagAiuEl+h<%;J+5*?B6j7wcv$@fOG zTRO#noGc#NlmbbJL8=eUl{$W{B^kVNbJoTB?Q_Iff>@`}LCskQ_hAb43La0GZh`hw zflznSDyYVYcdCj?_ihkXS=sTfm>YCTq$S7{ogBx-xpX=xA)bwL;s`J+TZz>83Kyl8 z5O<|N%qt}Jx|Q;hh)|GKrd;^KBLNR6tw1y&9;X#mU^cMD;X4E0$@tE~cLpuy@STP4 zIrs)BVxfSbIbe;eL`8)K;^Cz)#VZ!Z!8uZOw9Wx}CWES`=#-2z>4IRj-+5jOc01qJ zfxsQyKYKR;fhXwI*TPH3YjWY)(9|>Rm1=geaQ2R1b|;>>f1E;?^j3duQVK zQ0Zcq(IsV564gVcAptc_J&~%0^O;eshnqs@OBlOIj1s3b0o`3)tk&W=Q+nspF8Ff`jcc zd2?Nv#f{Hdii`9>-XWSrL;ZbQG>hv&VAXgLp?KUmCX{ob6X^y@3J{drBB)_kyu-o+ zn05=JSxg7Y0iVI;ikjw{c`=88hML7?%7uJA=yC3kW}O@Nd7PGbSG?P+`FGJQ&3K&0 zwPMMUI~IeTBXJ<=UA$3=_qJlaMo?eE9WIp6`btUy9SI`UvjoS(uiY6weT!p~E#>z=gp! zhJ?F}@5|JMnQ1>;ia#V&5v@c+9XoTt+&T!xn2ZEGnwP>EkqQf$T@re!GeXLa|Gt?-{s~&bmw$ z@Tp5lN-4K1sY)bqQ_lU^lQvS+VVCLS5k7M`RC!K|y(|-qvb7L@-~~6&;!3N443t)p z%QDlB%pF9<0QD_&04O(=Rq8S!-#Nu%->+b6C55v0#U<)_J(aY|yhO?#?@X)AjYZ!= zzP`<%D;gBv8ftPNYWkzl_*h9$@Tq!8E<#AI0v95;i=KmVlW@L2tR=5(%bHB45di(2 z`UWvhTiUO=ISR}aba(Z^t0=LQRd*6y6ZNVexjJ8TLgFDjn;_O`3#j;^;m(;SbSiI$ zoq$qMc|{=Vy9K;|JJw-PM0-IJCOLas+X>~63-HVO+-|bn7 zRA=z_>%iWSr0*ntK91ckeNfNabYx}HcSinYM;1%r*E_N<^xdTstEA_ZPV5DG^3H4| zJ%0>7`^D?uCGl*pR8jxBy$ds?egsKhXd`Aq*nnmj+6PxW`eUqBo7fPSsmJYtnrs!K zO0~fs)$_#(tex~xz5m$+_I;yUGRE1)LRm|VR#Z0cUGV+B2f^pXJ_~L8B%{%pbUWpi zc!fW|T#j+4AVgSx;5>>wsVF{!(1NKe17j!x`^YpWkcs)2_;z*#$3XK-Dql+^3=B|> z#hMYPW+bDEQu;#ZHu2Q=xoHb)046cow@(lSZ;8K%crrVo&Qq`SBTJQPyk@H3l9OYa zB=j>$A!`!hV4FsC!YW*SZI3s)eApx83w?4gV;bL#M?a_(NNTiVH_GZV0ye0ypj|M= zsdgiQMtp(-7tm@c$8*hy%j)fGvw6oAV0qd6u@!Q~$o^O~hPHDI30p6rT-_q9F%UN8~^Qm<{KjNDA3H)3+y>ft=|hBu;zq z4GHZ_9d9uk|^%aaGx}wIjd88{99?{mhPqW|nQ2(T%1$ysgMNYPKy+ zo7Y=Oy6*CgG&|I<9Z6rU!UfwFn{PzGW~#u|jpozUcFnL^W*?zzDh@#0G|*1z zk`+jeT}V_69oW)LpV@|?mE9!@%(x#HGT>1cMK3AxO__q_X_%K3N#oXCx?0rqzGDC ziuF9kvpX+70mq1Sa{A^f$t0jw8bCxUG;oM??8_F#-*w0yKh}s+$ABF0n1daF@=~~M zLKgT~rg4GcG<&odPBd^r77*hP1Rb^O529e}Xk4^2$ElVo{HqieX?lJ~z5llqcFxrK zak116W>K!nP3a-2$_6@Lptb;p!Q6tU-%t zh+Cz{AvcU^5vOSvx7PCw>8!2k)Yf|czH}CE>UZ=jm@u3ZNw8>NdqCSm0ho_bUU6xL zWo>!qdhNA_L~%%D|5|&B$Jp3J<9t5P#{Mua;SUaGNyfW4AIv67bzktygW1Q%F1%t0 zyKL;qKeV$1BjcK#O)|d5hdJ2i($TX#ZWwFH`weA38c*=pVJzPG2DcAm_Zf%s*N3q; zjnDEs6;^B9$_F}GXQRsRbF#_CB>#RV+iQ$cu-O_1ym3llH5nXbr&MU4VbuzRBD+3bT9hWd7(VHoNt77zxyDMRO_HQLmkktfWCWYuiAq?-A;J6PAo zd*=#7Pgr5*P3~kisld-i-pTHfTK>u3#ItY3tx!z8q#F-3;3{MCf#EC6hMRwP03~nX zhhr(c-)MH1>FYK1eAQ^S#hr@*3GiCMNv^aoh<-#HtIq?9>X$4+Ko89sTMIA^K}X3g8q&2mX{D7=nq;cs7lea4N0^rH&v-1Ht9P@b(K8+pQHLlB3tGgTK%9TpJhy<5`^ah}qwM z0@!glNZ78bt8)3tiL7^50B_81u6XuRGYom*w3v^9T_Cgws2^@V=$xp7pu>zDX?En9J8pV%-KIlIG$JO9K6i^kg9bWJ3Gdk!z;l z!xH)I91#}jDZsNfyf1bp*5eI}b!)Ew>Lj*MN&%$Kw2$+;k%bCabYTSrkm0YF#vz8k z;$2N)P<~b7G2Cayo?l_d|~45ye-yg#f$1htVvX=R*R>kk=er(aCtzrzq`kaXsHT6^gLA{OnZL zv!h6pNcVxvwIPo{OnziAkJhTBo+nIW6#(bbG&Tp1`=+zh*xd%q0icvAz)I8i@j-(U z9~dTcG_2?LOlR$-v2pzDbmo+fMDh3;tfS;t_^=slb~FCD(NO!??)5ns>}d)=fUu@% zm>5aehMC`%VSM2IZ0oIrnY2rJJz?6Bmx$B-0#!+6PVY(`eTST0zd$Lpx=4#Jpsr&M zn*bmm&tU<2%w)0Q)GKPCmdjtA$wqX*m!@SRIgCaBJ8XHg2UxGHuv4^2eF^4~g5khM zQfR@UoT7>7>c>I0ldP??6VmIz;qw$hE4a)VjAs)n5#)l2ORyo|18iIuz}UdFm8pgC zNCex*`Mnii)V{mL%sS^aGjH@D>lXXaYi0xeM&UOWzw!9pi{JhFkB@qgbxI)os6Xjj zEy~o@R3210irNgB>ZQm8|q{G@;+9AQv(Y~qsc z%_7X}A#CESlpaLR#HyynXL+uLSxSB~;mg0qFmMkL1D;ruk4 zYV=fJOwa0`8I#`=+LUeJ=`OW-IbAd~<~GT&3(}II+!LS6$IfEC(Mct7R!ov%`$46=h`ypywv?Q(SV_$U{UTXw^BcNM6#rEyLYcZhF9)e zT2mtgXgvzD5AIiD1QGNsSZEPUImqj363pydvn`L?A7UK=*Nlf)Mk;XNN~^P!e2KZu zV+#J}+sF;b!Do%twgANea*uV}s)>5KUf56(_b1mFb@;E+VVY%=)zcz=p zv;J}v8ap36TId=FQN%N1o3#6=CTK!&UBvW^aVbtMyI!s@Zj`$2`dQHC%M%(nvXDQzRkOu+V3nc z3?UN{gbF;U!w41UDlEVSjMxfwWPDP=pUGzf$5ATWzJ|I)mHMlwNWIhQG~J09Q6U}# zqRq2s*8^y4UXHE2km{y3;^w^=r`PE8ElG$$lJdeHxxCjrR%dDd5e*m@nLk*Y#(l(Z zpU*PHYTzd=?n~g;r~YF#0J)m4na_&7SfUkBQQ=&hw4+1N=wgPNTm}W zW65oAPNY4Fx^7Ca=`q%omRx!f+L(XF{_q%!Z-(j-O)D`<#A6mh@sP&{EMy%3#rTCR zVfx*Gm(Bpbptt!2(dHMZ&6t!dMKoJ^J_ttvMi&(Q)SRUh31%I@{uE-dRUia^q%M9{ zN5+a>NP&%7fhZLZt}SHUrJwrnmWx>TX4m6x7-{V3!(EFoN#^m{i`Xp*$T!LtPY4xd z2r&>_!4+bqCQis8YAJAiU=d5|IFbsA1MC8T;D+|&fI&Z1aHTi5EM}9X$9nTQi`h8T zQL&f}$K#R5S$m%RI19sj%Hu2rxIoa;x1p6bFSykKwUC6@&^xU{L+>=y^!L7pVNFn& zkjDqPSquuwcC%Sc5NpV9hLMRS>q$3rpyW{nsH{%`Ylk#=Cpo3CfDO^XXv?b+6Gi>A zo9Mtd_`0XgI6pgecSIhKEM#p`P@?wM?LTI$Fu%~6*}Ip3_3lOW zQwv#Q2F3ngC=D`5X;7;8RtarEPFKic$22@(scRYoE-zY!j}c_Qr&9aWH53oalH#q{{1J;r zR6-&iNu(Z8hED*d zb#x?^L347p?R}E(YUX2ty;+cQdG`J_m+vcPBU&P*F~19a<0TJAAy6}Kx0JPsL3fkT z)dVz@pBhvisB5rb8M&0TQc!b)Jl#uGNsLlIsvU*=L|IyPZ>sS#phhP&p@Ps0somPp zN)l_|S_)%ERK5qxH84mqv#Xwq2vOjY(=v<%ZT)La0)VxYY{emDRV5KqTs50^+%l{t zbvu!2e*7+j0R-TCECbuCI1joXH_a)jd#RK0C7xtMaUB2+C*2iAY6-P_iPM2W%)NS|;fUJ;@^dUeP|WaUw{7PAA5I!O|f{TxKfB-+^f-;J{31 zR5ugaowP%~SIq87Cw-2;iFve1-5!T1(q&vVLMP96En{(ssKQcQf)O^oOuRXrSPW#E6)O6q}+gU17pYA;s@0{pEf zSo?MWqIQg80y_#J;HC!u@C57v^LR(dpV1&Px_(k0#wpPN*tj_>k3XccSmN0v38&#| z$YFI#k{H{HZ3FK2zbAq!+P>2fmfEZK?CrC9v2;h z_1oP+l&NcSC?Zw#bAA$5p`D%TP^&X-X!zXbV(lq9Ki0?MJ0W4sW5jl+3MN2Gh*X!4 z-Pe!S^vet4j+ps$A4><$UiRtIdPBS0-PCSW2VDZ3C@5H!E7sbsp+;{`MAUy;OmqVM z&I#~V8p8Px>N-zsuhG_Q9HhZ)0H)1`eVe{Cb3(AfDNOEO!5ZI=DDP%s=X9Yw89AUb zk-q-{q{sLyjz)4F{WVQUB%^Bhhbvge*vbmCfqt9udl$dm`0dBRwL73>h^_d_e$ zK|It|Y{XzxgoR`ghW#=OC+*DZu(|6%WWcKcJMI1=Itcg5RY+Y_n%7v%z%AW1S17}0 z*q2(3t)vW|zM3VHq=59=s5OXRhj_cXp(>XzT+R9b$#1V_ouuWj^HZx?ukmTS>7H)w zl?@>*6d*a2simkEXOXEY#aprwU-~}3-Rmdu3sz$JIIFx;UqHb;d|WAvBwzKnaoW(v5`=eLY46=Y1u9UW7|i8ejU5k6xCRYaMTkgEB?skNu1qj z`r2O43pnf5=%LqXxzO~7H#l6(_HW)DpSp5OZCQx$XWroE_0S@Adz*J#&j#y7jRm(7 zMa^5!hMS%nQqK>pM^T@@A&Q#1mDjIlsgdjPxevW(&`WB)l{=mW&bz$MXFkpBdP$Lp z7bWd^noTwh8C=iXJ%f_&eN~inZ4)2!4C^1!0iSvyJ2vrWo?+QBZ7vzfa)EYUaq?l} z3o< z?LZ}|9-LG=Y~r6j&x%_7j{BAes}ZYRoex9r?(#O#9^tcIV7Ivy8oIb=8#TfzB5G*R zDx!EJQ3@1*-put*UxbxT*(vHZVSs|G*=dRu%zO>?wM%_jP!v`$IWE(A&ptB9K<=RK zkL)F2a!Bcs0;ZQ^Fr7dLs91{LaYMwx37$Br-j`{;u>f}BVsEk5R=kcJ)sumhHs0o_ zV$F@W1zM+(`fl;Oh@j@E<~30mTq2CMqOcSV7vf>e55b2BP_zwt08DyBK(@BRjS$_= zDzy6#Gtms3g2MWIF~H7OTud+*@4=~Q_Z@273 zkD_-Rb3ES+a+shNa4f)A4lJ_a7Qv=gTsaHxk}-%RM8H|7%k*1KArOu2mtrhL)|M8P z=Eq?F2luS!=hV)oym&)pjybgxP&5icX1aX*^!Xvah-9>35LY*{Mhq<)o5RaqXHo7b z*Z~~R0S`ohhu~a;1PIy)L5&2DcPz9<`S3K7zYAOW+Z;u3JOB%kQ*=MG=?IFy^0@%= z{2-@#5H{78okx_@n#%--G>f;{3hHC$Mp8d(?>!Ew9H`W>3v(d4X%|IH6F2cGo3Mp$ ze~~qxJaLm{=U7jsdJu;VH=%qS3*3d8uAGG}c_k{tdBAGtX$|8!sWWumMXOgW4#)oY zSk=|J)Z)j`Y2O0j7%FgVnu21~qBWGl;cFR*RBQO|7g=mfw$}kLPF|_XOgiDI9)ufQ z^kn?vi>!4&CDYfkg-ZpsW~O~&DQQGXLMYI(AawSGiCr}WPfN+~AYUA=heiaa>|lH{ zZzRuniM4NblxUw+?I)eEa8wU~O2Gu+ zB|tsqN~%X2wsx!!)S6xD;wl)5R`Vt=v!Tt1!JuoxbJKu*Bk%nVYtu?CQgzNy0j;@r zm|2-xq=GZ>RWGw!jEVe%msz*AwQh(8M%Y|CJpnih7)_(!XM^>r)5@){u#PS5)VgdJ z#LEnE64B*zT;z^dAe&*^BGaHPu0sy!JE;@zU5N2zx&YIIW%K8F-l9=&v=p}`Z5Krk zMzp6+vMk?6t~J!m^&m(p69q5c=+$*cjyc{!8ryKD>@6g98riC3xsuL1la9O6zM0nn zV_Uf7=;@|1mGVQRBi1xX92i`+t!X=_8G4phk!$`kVj=Z291vi(QdFN{VX$AR{XsBQ zVsX0tifsZI>%`FZ37xPWha=WwZFkwmDZVZ772pYAi7;sZG;I1Hoh7=xI+s$P7Q9Zq zs=l`gCAv)e$df#w4Df~4%S7z6$_2KmdIh@#=fjZZIxBQ6L#_fc0|VWY znG3d1rx1I{DZXqedK9`-Fvg7p^amt`yUR;L%0XM&WwnGT(L;Z`$&dmX{U2^{9Obb|i zCj@>Qm~FPsY$t}8?G$iXokP@u(OR7y%YKOix2*#!y1{LKbe3U1+B>n5xGmRSV~OsJ z{{tgEtus=jz)tLyv)~}ataMfi=;qWAlKLKY6#s&waOjB%;Ha(f6=bQ8{DY-V7A*C2 z14{+rPOzD*Cy1>=mn%vpw)(;AEav~7t!|`yEdGyd^}tPRwVG1f3hF{^b>sXHTO~X? z;PCS!+_bZ*h~K*j%XMOdtHAh9V{p)3b@0rP6BpN89jjbvmvA3DmMg`C1#nxK1S=*` z5N;OrnIph^HwFm@TXyvr<+No->=bxFq1y#M!Hsp^3#LnK_C}f`7WdAvd#{`&Rt%h> zs!-Z;q#f)tHwPcSQUwl-6xrayp{&l+wXV)p7C)?w^GjW(DwlVng46e!C2}N)p&1-< z!SeZ{CB_vVz#L*HVkA3?tiS;!P)=m2cn*O^7R9?kf%&D{eYiE5*wTtbm?e|V^8$S& zllda-6~D49UqN~o6r=SECSADzA&BvnI=7pox78zk!{2?1v{TS)@neEhh&5s#4M6wEiFV$N_G=jugJSW)}Z)R zeAQ+gfB`9wiC_31S|-15S^pC;yL`6=V!-Tln#OibVvwHEToBtjqGgB{piGaNEkM{t@E6`!#37FG6_kn@CgKQqzGVP@9N?f*4~X7hXmCm;Y3bx zrB%;^9su8tWq7xgU|okw#ug|zhC%wF6F7wdwOcdj`Q}@p#)PDDhr2qI3(>w}5`f(V2qxSVC}r?f{P zGKA+2At47+-mlU1fn_p2Y{q3njr5e34TadX(`H&W)P$wci#G{CLuqOBA_O3VE2*MZ zcB)vKqPpwhmZb;LcAPrri{4?kMH07b5Q?r_A79A#zr(t_v#2vLEDA^#Qt&~(Rp7*g z1R`3;q~Mr2qA`h+9XC25hu*M~r{xab;`H;5nFcNT0tqWPq8wN@e-g56vc!ZB2ER%` zC_0@u4e{T=0VukN%M#DR=}IiL6{~h+4s_f?(hxk$2T@8es232F_JsQ(8EoY%g~ zl)jIC0obugjx`qbZ4%Bn056KKN3u&js`_c0o9q(`fIm{YqP8FTjO}blQ|J}&XPkGJ z&2!bvcW!43S|Tm|VZ$~|8+=LVz*IhX2kWDofqneHH(Y`h9roB!RmebK4L0&O?SS%a zE>{f5%BrUgqm}F|XV6a}IhM*7o|D9lSfOq;X6n!(@Ef;+*AW=(vE(;=qYZ?evbQa{5MBD5{+49Z?PAa)exjD2{ZR=59((0xHG+ z^FYsDGdjw+r~{Ncby%$^>Z(c#@;(3vv z*Um%`pj3gk36&KxXg%gWcI@9MLBV~`!72}#gD4m;P(T&fabyAbtA?p79vXfZwMC+A zfrNQ&iG!mO7+nV&as&F*4TK+e7qw(L8ZL~ric6D$l%x5L)D2$%Rb}5w%2e2;m67$P zjt;Q|ycq{P(`1Yr>wcdG$3Z2uW<`$&n<08t;0D6*bMXZzR`18r6*q7MKcOMiP(M=7 zYKzKw)=JiaDLyvS!Mp6n9`8mzb2qyr{dSyB*ux%?ULV3w?qRKrGx(2t;6P)-5Z-1l zJ0ewm<^N$X`xf^r?&e27V4K>9-(tqPfB6)wQpLT#TJ;4CceMhhh%ap6t3G7Ww|&|} zBtgPplD8U?yu8JP*{WQdian#6YnZx(qXJL*OFj6x4_TahCQ^nR`=Z1rk(?YBs1?xl zYb}vUuXC_op!PQA0Z;k})F&-U*Q2^2ijFwNA-f0JMc#6)3YrW(fgH>`7kO)8oxDSw zQq$*j{#YsN)2XtSS~N06_oOp4S^E$Hz7dcphC+b*#-wfKCra7WUc-~9-Xy))uGpZ` z(eTM;SY~~VwXz=3RFBXe#wviYXmD-n>Vm;|QyWdpclW;+) z7nnl@j%XWiZBdPfG;87xmJs%Mid*cN~K1TD$sZ2##)ggtkVse`GRl zWFhc0EqphK8%@ zBfIeAa@M1JxE|{Av7%RK*C1*bGHQD;o9PYHNUNn*Xk&iji9fP#{H<~}LMn>q*Xa4< zRz9qPb&0D35PCCbyoTKG;DEYsq@;bL2Wy#pc?FA4*wK*cPNcH!B10_gex%iFdMcRO z$g3)tLz>)~#~x-*V@p2sFl&F??H|*C`xS@$8wmgCccEI#u*0Dh{TeFiT|I(2<1kw= z>TXWcfN=gbzn>>PRg}?FuQaNmjLs;7dgmy_Xg%)gw|dj33lwFR6ZtiTI56-iYn5{H z8$@~+ zEDZpp2LX6P^g#%KBmqFz7XJaj_%8sM_G>j?c$Bs2bn<)%Q|+-qqb>U-)Wn^7gw~1s zkHYA^#}+OfV-NJPRvV#cW^arkiV!N=i?BIpO5eIsIZoJCffO+&&@%1C(e?a=V>p%E z=WG7{F}6y|`ks$Gj%}}pj`K&3vpxeANMwfmsY>dzbI|AW+bC(zE})l4%=p@KcR}ww zjE;m2u7%3zsHP$SW^M_{M3xRI{F~z}q3z_as13`-VPkFGH&|dp?h1!wt~q%82{xnY zZ!jQ(yG+=%YCnF-`3cs(@iH)?-z?d;&3S`=aDsJ(pa1Vqu+eSCJW0Y`HOUwAI>CTn zi$zuis@+!n&XcU5(Yi16&lUXKNf>pVKfwo7vT@SYRs5++cImeBI11qM_H}uuhS%JI zn`89PVJPHDq8%;X5fDCBM!0evIQmYlEN5`Iq!LH~9Gg4}8pK86)|u zQ#fW;b(X(&iYdkw{MS<~&iEF$p2k7*ACK~Wr`ZYjyqKFv=T1=o@!VZ-!>Tp;KGd

XxSo2{#l;%G?8_fPTj32g)b@#`D)vE(qnHfww?rOU(~^8#4M)m_%2lJ`Lo@r zJr&wWQSJ%tvonPB$(YUZmkCEe@1UpAiGir~`}o_RuwIQ-q@Z5nXZXCGA8ckE z-QB z_6~Uwq0K~6HoDa3Q$SjlapaHfwv3nfg-_XEZaRmzsGDz;q|FiF5XQcOzf3I=XGAIZ zZarB0h_F&#N)8=sK7jFzv+z2xsobIV)bva$q_>W}#D-$ZQB07Reeq2QPmP1c&C`n; zO?wM952HLs+L?KMF|_6mz-bMiqZ(E2_r46u&ObpGB0Ys)n9|su$n~nqM1QG zVIXS99^eOCj_^aWhrfS@CAinOByS5)WJWr2@LS-yE-ebOw&_5Z&Xi5?wo0t6Z3BEx zJx+TM9qqYp3T5fjkfjc-f_nf>gSm>{;^<(OCCFk|h09gN$EQ}Y(J6;cQ%zMj74+Pv zp}Lo#cr9>>=Eq{VywT@JhGRjC?3#vPFPn|vzSuWBBwBGL zFW!#RI(K{$0y{0q8i8cGpSq0b|e^^gjLV2~ZoW0+Cfor9*mt6m`{@0Wx>!>H--RH;?LPFe3WIS9~P z(Ra_23JRNIS#;iN-fcPv2X+QwvJ^oKY49+j@680Fo(o0ie_wMH@pPR)Hg^1>Ny=9E zzH_kC&o}^hMW0OyCC+PWqY;GtxllQ2p{P*Phf{!>sp#!dDnn&ZICb;`SFd4dRbKNl zD)n4%m@C_&Q+1+_B;0@UMPPQYt&$$2%_i~^;8eaBY+7EsRsV)(ODHY`&t3{>fM@r~ zP$9waU>Ci3l7I6#i|*s4_i3R60z_N}a5F!yQ<)xodM+SFtpk&$T|K&@zT#~kPzOb5YnU>uI^O0qFHkMR6 z(e*ekvSZK=Ua61$^Jl>Qx*&lKJ(=6nlOC_g1QEcKez`GM zzhNESyF{Wb`l`tn_01Dh`f)%i)^$j)*S}gXOZyx6_oTnj_@*V=JVb?-Xs3`Jd8w{$ z4SB}~^Ohkmv6*0HSbgUxJn1~k920Q_kb2=K3AWDnc+%?{(QKUPQzY#a#s>;SyR+d1 zQ&WLA9iZKMgw}-!LLHH!Z8>bjwk2LkR!GsF=f}>od)?y@ijq@YCTR-YX9^yB5F`r95~l1oqQUiPQCXu-9*jND_{E1x0AB8uB}W`QPCQKeA%$nS&&s(7l2g zF4SDg_x%Wi#3j7`M|c>h-O2lE>^Jv4Cc#nKnF4V~wM+Zyn?$fYC^E?z z)=E5&k((YXU;HUn0>75=g+H_Gk#0*1>X$ z(J|@?h-eTSPk%>zHAT}gp|rR7N8Gar&5Db+K|!^baxaIeIC%R9tjoPO^?&o z&r4_ZH2y&mm%d>dM(f8pB;yun@15soFS1)B#=Qs<^JOZ5 zC|t17`Vw1y?-?vK$r#S&8ybLqdWI$H6g;P$GkcqsL3M}RV+-)lu?lwqOo%R><6AeKTt`H;)Mv94WG^lzlS4i|mI8!j9QrEM2XyNuuQ z8_OAfU^iv0LtPER@_Jlnqho!k$bCo~*^qmY9;a!9K|0)Y08^b^Wd&DF-1Ix!<1TrD zMy3OBX2u1gR^y9Ij3Dgd`j4o&P!N%&QJe2|VfUm^8HsjI&}zULlUOdMHC@`35l-SR z9hUU6@dzhzS7(I~ceB8W`tJ!rItTvosGTh?;()1+vJQHr_5>gG2a6f@w`eGl0y!Pj zb~dE@PLI&CL^q$*s{BTSDKCB+Sc@GuV~w#aZAx2fqCM-M=WqYPI`??+d9>2AI7=6J z(a^^zK+O!-79%%as+4j^IK~S+$@0JdU2;c`fhpdUlr}805vmP7cY13L~Q~x3MbT_`jMC;|xHk4S?gaqT8gwXW@c*4;Ng6@v}A?VzCgjP#974>C?in&Q7jzbiP zy?ZE1NO3?;Dg^{#?!&Zx?}Z8pMucne6a=i%DY}W_S`9OaF@Bdxf=f|4dC4 zykHNU^=YqSlSjwgX+1(4)BxLyxK2&JXuzmuh}V92e1a%R-xHe|T-QWhc%BH=W?}zk8j1Xp){)c&;SR zkrEaDy(CYP4iED?OtRb5Qn8a!^b*R`)Up#^9iwiy^D0?h)AGe3h>0lll!+KBZRHT| z4wIjdOhb5+a5=5zp~3&oyJIlFCtP0LvLnU(Bd=w!KM*btG)AAyxCzW=I+%Mi_~^!R zlChcJ-B^wZm)<(*f4aHcC|vq!Hh-m!{F3SNYuC85t=yqa<+5;tXMarX5OK2=E?*+U zrL|rJ^TM`r`#ySncx?o%6!s&gPy}4FF3M8($6xm;*PN%XsYgD(>Zu7E1}{yOMd5G> zDUXT8i{bROzP7OFb_l}&??<(5IW)dcYMXf35ZNvbdVz=8Wg8V^ zmotn;6Mw=X_jGR&sfa^G_W&1|o{KBSe52~MwR*T_m<8Pi1$guTxO{e2tc1iYjpGZO zkEGut+h9U0eD0BN9D}xyOQYnZ#D0@SdFb`+?Zo^M4Y5P3nHgdtt@L=!!|xg;Pcuq< z?|xkqJ1uuFcY8wW@o$lqDx8^l3<)h19|2%=bB=Dp~}S zyZJp?^899-aVg(!1CB33ou|D3A7FBJquvN3`7n`Z+#!1>JUby&eN7yo9dm!Es3m%Y zb^%a(0;aja9jONq9KwSy>NxTMg(Nz*X~?MrbMD|>?vxXy3tjwKcgk-XV}8hn^iU*R zS*G^@9xcMJwRVy>A0rR9h2IkbWGRN5CE+vjQhQO`JuL*#k9Ug%+Gz9y_Mhf9*I9FA zFn9oeevCY@y>A?)qMZSccsrpj42De69`VLW0Gipyp36@B}fny$)$P6ofe8)6X3+@{yw{&L|!vgy8!C8Am1&+$h;!>=V zz(HW{VK3D-FCo5CVfVgwtU0g=kqY1xm*Sn?9BLU<((w0cHK-Xe-r`+tV8Bs|Tdy|a z#ap})$;8R3;)8foZMfB=xDs#P;`i~NoE6M*BdD4_fb;}H72XkaF&GqwcS8#iWYr&W z^spKs)BCoe5_;~SJl3YpTAbsGEPe%F0E!LACt`~;kbzbJ1$6lrWrhXI93r`Fy0Fzf z0cUoQI==#>MHq6a8fn?-`=<5Byz*-Sj7$=1s|UTFYQK{Ua=Z+how;qwJeqZyY`f!zg*@~SK#tQM=&i$K zDH)0k&_G#=Hlj>i4zd6%THI0=qu^{NtTA(=aDk;|XSfn54HWrk7$9)&1>8ggh1m%u zX`oUTFlh^6Ly0~6WNhAyQ_sML7KW8hW1y%QLxWGeM{XNmR7}G_xQ6{1DdW^Ly4C)T zq{)%|xqIXeR-hAO)>8bKSYSA|@#6?}Q^h_X(U#`iO|mCUAA!TL};yK40*&^gb79cy@ni|pcIp3$=w(-ddGCoGoY~VS`pJP zzgzocBIJ#>1e2#O+*14lu!UUG+(d|5+OKzpIK%u~g$ z5IX^Z8D^~FW#i@H5%|WSjuTDfttOzsxDx{e8dok@4@_JzX4iu5&|7$0AAHl!`vop& z5e(n9P}h&Kk(=8SfN|~=Pj#40txu}6j5}o+clag(g^Ssur-*C$2Ad%h&#+vtq#)`o zKzY3a+g5!01ljJ^(-`e0J>#C)zrf6Fa4)*y5&zK zK`>DP71)LsPL^A{cRY*CG)OuNT?Vn9CmS-K94UZ_p}g?l2Ltwt!G3ck;bQDMl!b{D z+qEZ%3T?Xyi`sqvEit2^#Kn4vms_GlD?;5UlCBJlLxCuo@S_)K+ufNOYO2#({I|SI zutnC<)l)C;43$@gP{mUofudk=8^60aPkI*@eXAW%jegdW7s~B9jbf19-)~*l(gnnf7g!F$WOIr*lSh$kl^bZSHzWZLegYKYOpdRNlAbT0kAi-<*ae&&vDcHccOCL?TPn zC-A4Fz0P;uCwGy{sCkR{H}}cIjhA?jsq)dLox??{rOgm?H#bj{yM*ON;=Siso;*z+ z-RWr<1rhbke+WlGZRK*mSCYZFKXdsOfbk#K9xJ%!e|4J7jK+8Q=IQeDI3@0!fyM1E zoPWb5L}Al$dUU+Y->s>PT(3z?Nh!iM^0J(#3nC8Q$oVusi0DS&ijV`^gZwvq%8Y8u@V9?J9&0rI&R+Ex=bGs8&yGVtqzUp#)%h)kL-x0QP4`IXu7 zUlCIB{r;f~n~U?4=_o)?(~1KQs!1Eb}%2e zQGQ!`*yay=QU2B>J(2GJ`4xG0q;xWgzrItxDECDbwx_{(T21Q&l_v3m;LYt*!m# z6>_do>L24Da74~-B2`ED*L_O4ZY%P?@fp?-rjCtGyh-aYyZ@Og`F%<15a!Q5Cm)xk zQ3l@ay!@VNeL(VmeO^A)#B}m^$-n7>oN2y&{>iIQX_n#?fLXcnyA7wVOR;pzuxwp> zyxqERgWa-q`SF%`F|9owL*J(5$F1wadD8E4CsQR4=UKnY!%SaZko?d5E`MT{F5K&% zQzsvkOey78`G)+km{xQMV-<%lR#T-ad54HY8QXZce@4BW&`A2>OaCf!n9CHiYR^@; zkQg$@Qzu(WunUQ`#PY&TPTBuQSlAJx>Fr%t{bh~9MjA~M{a5|Xn}o^6wr;0vz@pl4 z7QZ`&%7y@bONYq@MUi>mh_EG1{O?^Al>X`Yb2fA$zM+j+b^B&{lD0I_o%9>?+rmvc$w!siC5hS_e~8LlRA0^pw)f%oNKyYFhY4Ekmz4VBk#uk@5-wt0uQS2{}awXJ+{2W5jitc|x_ zMgjVCn$f)&KCTEPD&rkf0y0jD?2Hj!ix$-A$|ILZpaecE`#YbwvoKfH&tHV zN$F_Gf!Cp(l{X#vZ(*NmuLTzqla~(Tb!RB;EXA*?-rqYb!(~g{QoXPu>-*lUSy1V4 zetf19$yat&cFA!Yy$^I#f-LfRw|7)erK?qbFOu^<$|d>cT5nEYrKb|GHJh@0G51Wd zKfz-MEA7J8FSfHape3L=pvj;oK>Y7eK9@qd znpF62xmvi6a<#Nj<+G+MD=d}kRUR-y>1@egi!Hes%1GH#I+XI#4L|%V{lv9;UjA1; z^0fEESxRq@ZxeGJ>lfWLb3-3jF2gmA@@dPP*mI^C;UTOU1Zby#IV9s{#Ma&Ys_GXJ3Ib z_So5JoW%-&&tK118ptoFdwuhjGm5+|jUQa9wDwe}Vw}V?%F{65OEfwx*@dUy#W>CG zehi=WEE6(ZwN-J(KXGRRq`lhpC1iLRjG|92zG{Ju-X>%5=1)AVhHtoemAurZWr!7| zFfS;^!+P7RP{2;b?}9D0jeSy}{vKR4Uuv5G*ycDg&)jSI>{&P z{O?>PDsmkbv3PB`(Cb(Y4dX4P;%oOS?7Y)5Xg+D@lb0!P1rHjBTnb)v5SoW=UB~h8 zJS93o76qE;_y(c*Uc<{2ablu{Zx8l6@KPH$<~kpVXyiH83HwmkAGTe{;8XLIq`+Pf z^{GBlZJU|FcjhU*`gcMhFDmvp5C<%ml2B#hNAIsLS!#=fh3_*2{!$q($GR8v>;Qp0 zvy1O*gyWkI3*Q(l*%rh`Wbp1UDIy#8bMQ@(F8AfrXbEjlwZ20`zFtDHz_Cx@KL+SJ zZ95cKbQJ67gNRu2f|Ba%!5-UEC?Fjx!2}c>@O7|5wsE7m^or7~{(yG3s%K^=k9B0j zfQr(o-7Vhj6(v7p1x+ocX;AzrG13O zC#q%7zc1l@Q9az3L9NKbx27joGycVD#l;I&D~^coU<;+vT1xST(oA{8P~Lfs(k-;d zC>c8rB~t&Tw)QYqD0spgH;TWwMrq&fkH!u^*zieU`ibtxox!3vZD1Rp?v zZWc?;sP#km#WhOnwzD8wP@*2Tje`Mo91oPo!qn&Hes@;lf)e*(+fYE!l`pjo9>SBj z64|~LBuPu7qQOK}iW99!Y%VQ0;MA{Hbu8{`YdryBuP!#O74dB!DfM|FSKetl2^qw) z8lh;@Ve;i()r+#=URSmRj&F9Wx?k?X`7B4{b>C1vm0eZ*$Qvk}m`8ZPn@UuhnFEl< zTh;hr9%@+~-&5Gt&2^+hh+V*rCw=b#i`85Rs`u%KdFq?WfG}K-XVo)ByUs_%s$GG9 z2sc;z^KEY`?ncM@qgK{~GZf_myK2G*m@iXU z;(Kb5fA9`(DRGVa!dkqpEEe_gP%8S>65m{?!oxa;Z7m+=&%UKZdGNGHk9d$zbi)fA zfAmAFitqup198SU5U2N|j$h%yuEJpCXO`hdK($SNSY}01h2n#Rbr7SXaAVZ-+^7q1 zQ4xg%e+)zQF-*)~2jQD;uU|)U9S0#@bcVauV(Jpw^cEkvR*95zH1F)SN-HV)Tpv`G z;1_;ELmq_hR=OFR6)FrYjLml3M4!U<7b>@e{^xHiZLtc`_^Q{LOXBeqr(C zdS$T`vLNynK5BYyP_7See!|S8dP#eY-+5Pw8`56WizILmt{5+R6)C8A`u^PAf2!vc z{n!^i3C)lr_=+Ryk2q(8p7s%Z3-AhZscOW`HvRMZDOgS;Z$G`mGdCzMk8)43k$9(m zUe9kEVtulB;h&iztOWcNwlA z(HxeGQ67Hc&Z6=C8+60#M9gVUqaaM&(4$fm-dz0~n~0+Ez^CtFMv_m1`3Arz_yNCy zOvjIU{CxUPh49JGqfdlKC{f|j+lEK)2#@Yy8?Kf|_5A#KUiU}5VMaAR;gNe9vC4tV z+*^ptY~icuR^aQ{M#8@>@DFdhLg658D!wg`Bvqh4?1j&oZ&YGky@gZwVQUKmZb!nC z)_?P-HYyDRCXnjkU%bmVDqW?3D?aF=(*AInpL}2GAp3sd_D#x&=-!uw(;;Da0g>3g zxHDVBC*)myApKIb!T8Jk`Ay1D`F9`xa+Bh5j)XU2FBz5S{%Y4_m-u5JC=Kg2)1~kP z$FC8;KQHmcA1Hn0>s9>N2g;+Nxt-C1te92BEKT&C?=EsaYDQPN&Y4V#O}Jd;)!gigCVQaeWE|Yyt4xLN=xvrSzA*fS!#;` zLv7OAuNZi7_st1Wb=DB!9ln71&Cj~;Jq+(!*{%v!rjKuw?(?A( zk|!O}nXo!Ekf(pBbd=v7$X9=;H1*7bs&4{z_&OdzptA%z0hHV|>M59Kw$pcVm-c*E z_wj3APg8TK(0tF(OcI(l#J*x?a&AR(UZ=weI6;`#X(QO6JaG-M*yyHi_Yyg7b3z{_ zJ!p{3c$Y28c)4)`U%N$VGHR~gIR^#$X7$w-pEj@spQuEF5B>zv4YSc+_G6~JHy$l$9P~}|w4Zp?R%M^|IJ(y4v9<^LdwpA#Ubdh;Vjpj& z1CNkW<9UZql$Yckt-YUrqP!u?C$GLYYKNl8Nte7&e4$j>WlbA5{j(vySrYqe^$V z>rwv6QRN}|!Vw;D4Atep5#I6`{M~SbCm&NDYclAFUa&vomBrH=4r=x5pzc>^)z?4Y z^%dTMR_$tWgdaSn{3W+3;d{SC)x3PzYx_!>CpCEb5Qgiy&I)I4GCqTrtYRCc+V#jG zUT|EQ5T6>0lm;(ai$`4-!{K$4s*<*N8D2EMXrd#Jt`kMOFJ1*YHr|Uk1lUtJeO|`2tj2Jls5mC|iD*OHVzT^cN zF^R}@v=`ht&vU)=A5n3uT_@oN4XUwtFqla4YS%*HspxKmKWE{OVQn<*(ZW*nuc$9e zEsp4>yv+$ETK=nvH|2yfRhHj!^Wtw5mo~bkXpT{dE%}e%C@a)YTF96Zz1M=zKBWu~ zeh5f3oaeBFbUo05mz`3&h!4xu{Z>iPy10c)TbSGXz_&`4)Myv#3vP70CkC5)LwRzp zmfYR3rV%gykJ6yo6zKTGjS6J*Bw@y(OXQnFmya^cBOCFk@01+*y8yoSJ0(@SwHy6x zu5C^L|NA>-u-rC)CzmOCJ=SU>c-$!NgY|45C*DAgNxrR5pdP8eV#G8-Ow;tfCC@QG z5`k?u0?v!%b-!0y%Uv|y`+H@e#{*@w?#kf364p60k;{(B5TWP0BYf+AEGx)&9pf^x zDOLB^$XDIxTt{7qe5ZtUuEQamEr`9-R7RC3He`Ja*>AdRvmxtb$YQ~m&s`vd3u4^@ znbRN?@`~dS=Q&>bipQK*x->2ihc)u|O`P#3Ehu$2loGz;b55g+PTIw{oK_Y(SEyLu zWwoUJf$d&z-!sZfR(Ye$kDpaWMy&owWa%eZc_IY7U=C|FeQCSsBi^T6=_(KZh|eom zx-^&uy^cq6tEyd%VGivc1!u@lx`F#|J zKZka7wG83Q&MBj1Z5zLOPHE~oRRk|`OLpOmd$Fh>=)#T^@!0dqQ0F%d#gXj&dS_hi zdVCw7e;!MWm5^4uT5l6|e`W)p_lT$}W!0`Ix70Z`C zh==r!A^Su)1Mof!6{wBGr(ICi%5UuT*85q}q@WWGP)!@4s;K`+y!l0?Ys1OdEfW*z zmc9tAB8+)gR#dJd^^U~nUQ{}0%4W1xM>yc87nP=#>wio9hl|*UJORT$7nSCg_h5*- zq%@VK&HVmLN-Ik$1amJbQI-T4UcUt0dNAy{1YO^4d{Fn2(l;b`lkoM;+gRO}iQpUi z23h&ui%OVfHdNAlaP%+?b9`{r3WoJQ1h8lmKjKr`SpM^u#Bae+f5Tr!As)6?g&0eiMlP%b0`)2!~L8m}afPi`t^OBEpyd#3hq6VktA&oTOev zq=d^3=ogN{dRG#9Q@rtW!8yL;7o~oP2&E02z74yI9WWI9De<$vDB&W}fBmAgb;YB* z+!+a17^vN%>&$aS@8>-$FkY>ApFduqG;}$IN|d3(3>9fV&x1u9~tk>jL#$bVrKm424iOY8I<&y z@g^9InQ<~c1bJ0y7_@*?e|i_srcZ2ABKY#FN?84eb;ZjFp`S42x%wCLU00RIx2hQ zL6|7!*-GvFCQ+j9x7?gvD^<1IxtG_8R&I-6frnOAQYZZ~7HXi-EGF!f2 z<*)s&JS^MZ=D+=}gvftl3*|Z@{}fM&Diy)%>;8sN@n82hgD3AIhLQ;czws%iyC(mP6*# z{=htV{uTbmALz8xzO3R6ZYpkPCyfox?O%!g>H%L=yS{#t-*;2VsI&K26&|vkygzr@ zVQBE3HZ{_OsY8%T3H>K65)Hy+)uhgd_TD5D`3jV014!4y4A$hk-s$H@KBUw9S^|rMx z24()>v$k+=sYQKClIP^{9yav>`L7?m%fKah(;4p%cJ;qfXvaUqR`==Tu5wmr?tC1y zW{YgWKk=_swNYq;f3Z7r-fPj+p*Fee+ujH3sI#o{AFq3l)Kl$p@VmIPC|1-*q~xxS zN+16*MD6O{gKgT1P<;o)H|rzQ~l$JjB%>T z_Vp#+JXB4RpS$Fp6siVGa*w&*ISo{gB!Bj-_jZ{2j8zUe=$#y;_Lt;&Grb=*Qk{}K zJ&PY`tUe?=vUs>#{aOBW+L~x}MVp_B;jcJAz9>f$<22s9bQ%xRU>38_Ol%!Y*E1E* z5~?!2pGT{TB)2=m4>wUa$jeXjXPT<3<$}H39;2rBd-r=dTX6fIne`6a*1!yeNn+_U z7*t#fZr_<1a@Z!`o@R)*L7ZDw(O|*tzwv1g@$3kXg}&$S$EY*poMPT6Rvj(>xtn`p z)mXV`s&_-I`hzS-?BNUJ)b?_-{oEU;4wRSg=GAekTi&yeN4HS#m)q{V08=lc)k= zfRm`jOF3_?UJpGv9woEXwhq^)t6g6i;H^?#*hYO)eri0owpG{2r^oR(+NzD^z2o?v zw(36FHjdA4r|xYyI0J4M6~lQI>Ld2b_2CZ78{b#_@p!eZJSKzZ#jEjhyA1wCyn0`c zi(}zn0V2}J zJ|-6S9izVclFx3hcBpf#=$>rrm)zT4O=)nyxD&n9@qod^HC>>TFL|R5>Lz)87C+iS z?H4uT7#d7CTF5|Qz{8rJ_X`8w^4zoaG2XJH+Ei}7l@IHv&a7KB1F^w5kU^FKVr+bW z2LHAr%If(oysD$xtNy3ysO$K!vY0a9EO)N!-E=-EK^^?yxepPyqsbjzWq3j>qW#U$ zWSqb?9xEV9dm%<&=GqB8d{kOj{m@@^rUk3rL8vIX`!H?=r1J|2>VtCc4|$hPYK8o0 z36JRv+;}UW-B}$g*ZYM3&{@4GpZt&?=%QxI6KC_ziKr0WXY;X%s8i}}z9vzfFF&`L zhj&%S$WLPN*Hw*>A34Aax~enf#+!I>H+7(;!ICPT)=iDWi;U)XQ=3JEsOY-0LwjC8 zq09(QE=bm2`g+*;m*U;qO+78i+9UkU9_pNW<31NTjl+`NE9Z5_CiKI*c#EED<>gPs%2^ekhfyRmrb{oG~=m{G;Y4O0^WI*YgLZ-1d_XO*uTrZ#h}n~r7C-ivau zci~PqER*wAINNxC9EQ=v(&L#b-ti%n3zWw{q+YW$n2LN)RXh0mvMN=Lac!USZ@zq$ zszzEY3!bXtS4N11sKw z-Qc&p*2mOvNgn+=Z#+tkk_#8}UZd1CAthZz`m>@%&mFHf!_T{TeWTPE$y1dli4BUS z3q>!6?@M-f{Ax&M8l4MAB835#335$NPrxq#;~l2;VS-dkmp?Tn5eM zdHmWFYRus8m+2{e!SGmx&DVw`(~zJua~w1zPZ*LkNNyXFp@t+KlHZnjA0Mr*kmMKV zdn?n$2)en0SAJ6MB*{HJyz5wXvt{uURopjLjkPRXuW)CEy3|KS| z8?T=CY-$DO?0@f)k6x0N?X`Rx8ZG&M4=nK#x&{v>SpfjNJpv$02(4U}xK(_S? z3j)*cDUG-wbOh6n(#p?&K*^DZF zcnSATRY%BkmUHJbYJwF$V2x*q;*p&5}tayoUnV~igy|zdkmyh#}4^D0Kv2JeH=7UB2rx|J!&v(GT2ak#!{i4um*Xa^? z@WL|8hM*nW6>$}B{%C6Z0(wK6I!eF>oj5nhvKO!Eo{Bs0?P9+bE?zh*8r5E3>+!)A zuBN(Q6^@4Bm?AC2x&T2O#T#Bn2JJ;ePJs5pV#s3V5l7pi#zIjXaciaXk*9wT9*GxF zjob&la=a-ms-rGXbKKS=I}$Qo2X8-2i&6|bq5lK$G5FJFhN0(ZW$<4S`epHcSu0kG zu8S}0fradYW~|(G5x31$+ck^RRY%$4b)Hs=pyB-!;yqZgt&Qll>Hi{sbf(&*Z>6Er zMm3^)0zsb#odgNze_gzO@Yb0& zU%vqJt#)k=g9k6f19W!q#I>uC91MQ(CsYXNxNZxb+oNI^2zR&IbqYin!3$qDY#mp1 zAI?PmA^5o&*QJB!P8~UL?$+}q2fsfP+Z(!Y6Zm&CbyzPuyDs*o<+dK$<;QVjzM@=L{n`#7$%qiEU zeRZ{w04H{q95{b&=a=xnF&(xxxN$p#Eg>( z2QQy-braxL+`fY+{%Z5Y3%n>>4b?u)(d)vs7x>rNYHaU~20Vz!`dVW;aPZbM=nc>f zr|r*26?hibS*oW>lzGUf>;{#RBTe9Pgvgs-0}|&tttSo>zNH@}cK> zsYmVA>c!_J7Gv*WAAm3X;fT*{K(H(@{RyxWAMILj1<$o?2Vdb`bJSZwr?JSM5i&lw zt*vaOx7U0Xk1KZtdw*DM@y)~!5&waBC2={z@Hmus4DlYshZ4^s zo}+UQV=t574dNS!Zzq0;cp33uh*uF;8&bf;I}lGMoAp36AhtAqk3z7ZX2D z{57;iC@MycCqatc(`t;Jf!H{dH5dZD`shtktx3zs%nM7m%VRjr0h|0 zc=Sy*B;@ah49Tf22Jdmjo};@BN@=MXzJu$!Jbs1h^ql(6@Jf#g{!&gG4pkDQ6Gp4o ze-mrjXA?H^g0Urp14zH17T!oW$guaY9ko0-K-f+ml-9B@uZ1fKn-Q!cY(_vmV2!bXHq;#=NV^^7sB8H*tWD+%HWn*k&e7G)-WJqXJNWJ$Gf|62OV{`R8A4fPiQ zr_{n}gv}z!sAWHquoF%^&SS2#aT+;+IWWJn$61f?D=FYT1|7vacfC)Nsz+|J5VxVd7z$_$3(v=GGBo6A3pa zyo7Kw!bOBl_oak!9#sF86E=(B0%053SLzt~Z#rN<8d+$@Fov+%5ycZWiy(%0Gq#<+T*lvqpqkk$oazGk`S0ZnDoI+=TE(!f}Ml2)87x zmMb|P(P@ZhlHwN!fxy%^vqZvmbz5{ygw3wBcF&n!OFx^i>3;!XvyK&&D;_;SGmA<| zA)ErN9YF1#u69RNyPenW7nV?~F?-7RAL<$@Hd{eux#I0tpdPZd6i@Bosw$ zLy3nxO$roH$^@DWc+4P8FNczsCJrKO>TM?*NN(^9 zb~pFPqMW${NXvU#o;3Z=wP)#7D@P5uL6 zlb6@BudHRyt{VQE?wy29_q=qII>vL&P`W|9ium8eS*2lbC9V<=A|68AO+1cxBJuvj zQ;3fuo=)78L57LMClk*ko<%&H_&nk{#1|4@LOh>%0rB<3HxoDg-)>+J+f5b+h?f#C zC+;I&Mcn$Eks~T`Cvi9NMB-+9O(vW|JdJp|&e4}nBta(eY~ne@^NAM{FCtz-yqtI? zarJj2f==RL#NEW>h{p>q`tn2)BoR+0o6K|GmwI`JjM3$H8QiVxL8mIxA*6R#xBZYbVk+tdf_9@UsHo3ptz z!sbAmPS~73Pb6#(yxD}!aW#jqIdxe=*zA+@37g}tr+@_J48M@DIV^1?Yz|{ZgdNoH z?I3Ip+{J{=i9iWqyfQ=ol@g|5(8J0|V2)$ugzJ+7A7OK{RY}-I_Em(%fg}B=895q8 zP$g^*gHFQ6z>jJcMgntSbrUwn(Ky29WG$X>BMKmqaAU$rgx!Rb2}cu7A*|0VL^V$% zfjL)7Cu~k-CK5L1I+=vc$y+wz<`h5<;TD9K5H_bO`Gmz(NQBP{NYI)b6cTPjcq8F< zgo_AwCcJ|%o>u92`3Tu5$;2{l5ju5RfPK!W&uVW z7)V$pd>>&a;Xy|FQ3pscm=xTEhY*e<`~cy2!ovtB5>6qUMEDWH$%ID`P9glLp8pW0 zkzgDtq!TvnClZ#ZBgiBy6V4%wS3&E)e8N`3=y3EwA8)JHe}!c4v6G;P1Pb9|!g#H> z{wpPnOTLB&)RT#~A&6f&+2gYs`md6(gD?v;ilh!>7ZZ*m zTuQhR;c~)_30Dd%%GXT-7Gz`*zVf60oP_b^A^qnj+>~%UVSKAX|0NN|SDy4=3gKpi z(+M{xoC&NiSz3@FhZOKxFa4KKxE0|-!uWof{wpFZzMLk0C4}1%E+gE5u#a#8;i@3j zSh92?f$A`Fpflkx!uW`l{);1=NH~#jSHj7JyAe(!+@0`5!o3J*J3K~!_>8*#TS5v+ zgbN7wA-s`rKf*f*_a|IRcmUyY!UG9c626Zx^VBgSFo*!kL7J6ZYhg;1Lq!6COdhknp2~iwKV+Tuk_B!li^K6D}t_pKv8%$(WI# z{s$X5V5KRFIaN{!JIP)n>?RyQIG%72;UvON!YPF76HX_L6E*lD{7e#rl0pvQ2*UY< zqX=&#+?wzX!rcj%67EH~oNzzFm4u%bShRoDY2<)JbF47JR>Ev)=F+Ct$Mz}j+pXol%a9?G*C#=>tQXD`y%)lNd zwKg2Yk%C4z(R4sKnXnXZ=%<T9Smb9zo0$%>j75t&BzDYSwj$co9nj0B~GPZKUDEKUfBUnSu} z9f>}rsS&_(5;zHeP1sHNTf*^#PZCZd{666n!aE746FxvVlkg7)_OKihnCpXl!e5eu zLc(7VE+SlF+S7W%TtO9+{p)03M)+yM>7;Kg@ldBKNntE0q)`@&O$GfIW8@H31?D9D z99g>wdkLqLdvlv0k?aSMeKy%!1r~i;GAWoVo;1RX$w3Y|F!vEAl08*LX6_r9`wH1) z{|>odLU;w?0>XoUMfhwZ2|gx;9fUt1oKBV0T+)@0eJl^38<92p=IFM|dUSM8dlWClfwMIF0aj!V?MqhsHFPO@dNVSVDLR z;R3?Ngf|lYhHxRJP$Rs9>`xFbA^cy$WrV-e^B+PV367FN72!_^tIdoY*-JQ#@F~J^ zgx3>JB>W-aWWr|&rxEtNLV}4TC?}jv_%p&w2p=b0K=>TtjfBq--a+_B!X<>iCtPM= z4?9GHOlp@+3H!)_xym*-Hq4!oDzcwU`b)^YxznOHH*#zu*_)eV0ffVJd*uI65|}$5 zZ3)Mb2Qvs4Q5gpkP9*zPgp&!sOE``27Qzz=i(y3kvIQ39|EUgff`Alu6E-&@u=S$< z3dnv5VJ9_9b1P>f*_(B02SqrD^mmZGxiKQPcJwu+xmQ#|3R&bJjIxv_LadDJx01b& z@S}u_DL@C|DzYC&xPbhNBdo^Jn(`?Uq>w^m!eMbn8IB=rZcx@C97pyv=(19ZKrq=S zlKpDJ$%G#yT&B{R(n*3eQW#Fy+#PE}cp}+PCv0whwI`fS_SuBZy{K-4myrDnrh9Ww zxh@G(s7!kkE+7X>32!9)5Mgt-tpnj5WdAJT9LmvH!X;!sfv_i=6kcp~A437dP=^>mE3l1&P0NMQ-#RKf*>IpIpxNMU`#8_7P8uu2KKyW>js&y#%> zxepOowAB()FgGBXW(43O2W4bGg0PQp0pTjb%LuD24fksahY{Xr+LQmGz#@DWM+#}A z;3S0xgcHerq=^HJ0D2HkCi_K%(+Iyucp~9hgmGg_-{5@19RJ-EU^po(A%(|G56Hd= z;R3SHAiRFo^B=-=61-|Upa2^Z&L;cU2rnW0IN?gt??||S?B@_R_pFl$ZzOwh zD^CBJyXuLAcaTC3VRwL$;;w{C$bLTIM2bKp;WDy+g0PS9RKiJyzK2DTpo$bm6J``( zC&FrLBLedXr%`}z!eM0pB;jPTZ$vnb?9&NX>GsI~1QH~Y!drw>2+t*)PaZZWJdx}t z5zZ$34B;h&ErbgQpC`Og;QImcNw9+y-X>f^_#?t)gf|iP5gtgmsau^nW_GAUWo+os9W zR~opopRCJEdl|ea(ct+V3?Bc1!3+N~cuurM*LUU{?oD1)MgGsy^*k(nuHm5g1%p@S z8oY3i;X&z@n!J3J;ep9Bi^#(#40lB%$iHC*FTIa&vf)qq0)Nh5nWaU9fZf%%kd!@bQK5u-qwq6SI&o*y)6D`cHj?H-2`@`;rU-Lq@ zmdw*zXg#dkp6&7$PivvMt$W_<)CkV|d&^sB#j*9>B{m~R|7&pf4V(AlVVc7dSbURg zZri-ahHIy@))udbw|O%k z*Hmjz@jAn*6x=ufi&UxJ!ZF$}iY1|KS8v`VjR%cbIB#@1Dgu*1`UFY5gm5DWKhsW3 zCB?Xv3cJ)=b`Hpw!SO`cO}uBv=g!imTHJK@HYoF?@o*~#TtvpDXE857r^VqQ`eTvV zWWR)XKJmnO_#n;#ugTUzB$d0ewSJxp2vU&viLUj&eyKwl=5pnqDy(hRxa_Q$DSCfX zen??Sus3Bf@$t-kSkY^}A;W|XpM=Up^xIC#7?U+7M(=hIStcf{ZR6v|j-P;sufQTh z+V&fcufO#3^nsZb))YP}WE*S92I+c5khx)JI3Cnb=#|#g)7_TUkfrEu%OOL$$Zd)~ zP*e%5*&^WR=Lyw$nupz%vwNmB#D;|Wz!wjpUm5B>ySUllv)$D*W@T=TrC=f0mJ*61;!MSa zuxcPgbG1gDh1`ZWwq^vfX_%3I20B2xwQ)M^glzX`Di5BgHF{t-+~V~r>V>+@ipgd# zP^6IIdD4gjh-*f$E_)90cp*nzg^oY=bI0exPkFb>U!SLSvQ+L@_&4*km+I{?92Ung zRu8L?#62p1<~gmA2hVguQbCpV%AKWkii0u()AfWTpjoy=Pa;Tni;VamGQ`ABC%ids zT}r7;DYj)=(=G9G3U>AOD=Y_2qZ`>-qZ`9n{RZ@#5mp&m9#UGb*qIre&Lcfq zYs==(RX)U{Mb(EvFM*WaxWWb!7IQsX3s2A&hDClKY$JgL4sRB~LZp7oQ5je+qOIpk ziX1O;MRf4tQ8pGR#WCa-q(*MVBc6J7$ZE?92jSiuXd?Mt6T^dhI<0l3aJRzhNRydT zZU$v|nu2*nxJzdClX~DhI7f?mCEjTkNw?-qkpJ-zINv6DdN>R zn(MyIR7~4Ir-WzWb{2jEfo0iH2`sw&HQ4sE83)u33=y7kV;{Pl+?z;ue)wx@8>^1J6~(cpIoZde*iDl zmeWzo^s})PxY-?hDvw3VXy%==x)S_Y&SDWFIcaOZeV#KmtAv(812DU(h<#3AVG~ z421W2D9$WjG1!HGPD=-}ctvaCc>V=xi<0sHP^T(1)Wv(m` zGRTDta&@R%uJ(xZF`1Sgd8L=?cDbDd9R+R7M{bm5{`uq;9NEIr?BU3cU}w z(TgR$gBr08{mDTi(u6&_Knk+4ASr>_DlM6MHA=CwlndQh^*xd!}B$FVtcAUdH|aB~oqh!~BaEweVJ==D1N_ zMuTv$_+1!1L2-wSEb+tKvRG^1F%z@U^`IrBP!pp!%K)K|@RRfEC0az^rYh=NZx&Fg zmfMT1nU-{;uP&7kI1V*Moa)Kc)LtyU7+JFy{k$20dzgD((jq(GoaRojY#@q zeSphyV2}&KAQ#BOhr254m)9-T6POXeoJY{FfilRgKh`VeK75TXMq@EAxGNi#N0vqu zhh;WM4^45!qnm#%knvMXwJ?3`zqVA1_Bc;6_9N(m>75^zq5@e|RxGMu42w!_ifY&d z1?Og=7)r&m-?!3i`;Z1La84s}H3ykf(gzn^sQJQ|3e~_!Sc_{NLx-?KeDfnZl zvBnx_^+Kw8B1JurqHfHYg4p8DYD7j`7Lm}JMIe=C)0gm;I7Mp8hb+_jG;~%m)*a+N zYJ}{E`FqQ>u6i;5v`lM(Pe0(x5%@GkK56=4-aJq1scTNi)1q}lu4&ksr^WV43dOr_ zK%}Eo!K|hX1DKzV-g&G7*v}3BY`GSt7vGuXT5QAgffCyQ zDkYbGm^aMVx`YO>$b@hf`Mc4*1Q$CpgVF<2_~d-e9a)r*5e}~|Njq*N#UJzHd@ZeE z$}Ne#3i^^X{4nphLhITw<&MM-|06N?*TM_*sRz&o#d4{J98;uwn6F)-J=q~TkVPjn zWYJPR7NJiXLW=8U)=k$Ng=j428&-ywHz;)#*Uzk%UN^-V&-j8@v?o2`m{WgrH!=E% zV$79YENPa^u7W0(8p)}NQQNX=>PXE4SaYd9tKEZ2s9{YpPW$(&`m*tci>0U+LK$|* z*t7*Q`;r2xfyMd~ib4-NXzXq5#F%>iSkk9^sHORE?3p}lwHB|}njx#TMV=%azbyx) zour)d!_vEf?A-&gELLj98dXM?N0iSV}6S@_ACay=hulmQ6=I_?BnvXCJIguOrZbHhh)jB+_RGh5iv z%JShrYM`(N7?t#4Jl$A|8Wkm(F&vHU2-+i=UXNMX{87ki zVJ~v-R2>~Z^thF!kQ-s|kBjfQ;S=CT-*hWW2Nei?oUso+6@Vpcpe~yY`9|_b==fuH z&mBMfn%0D$@MvKkoM{h~8Z&FDoGAvz5>(-693Sm|BZ9TL(2zAb5Q#mDDCSIUgqeCQ z%b8+jEvH&p{C7qI{jo9V^)UuL3@_%iv{HhfqQ^Oanw~nC1VtB0D?=MPZ(%n9RHDZM9Xjk7R*f~~D00Ya$`IN;zj?bi zw2>a?O0;N@UUNh2EaZaYZtWHFR8W4INUl+PYswHfYBWO&sNoO7@yGf+q=Rg%C9rdo z`HGdj1p2#7O`yJ1)dg!Hw0>{otx)jCevWweH_>p*SE0?Vwz8q*zz_2oZ)!_D`5UaP z&wEyOg%td8c&vsQa~I5*+ZyxAOeJ0Ha_DnZl$ajn>1LDzSmj6X>tl3urztBEYRaS# zY|mxX!8VZe`Daf_^ZP5$}g3r=2-79L$NVaIOr>ltdT4 z(i$&?T3P6DeHIa~_W;;UN&3^uwt~!cpg)$PgIF{+1)?!uiH2X%!=ox2mWP)%D0XG? z^g?auh)kP})wkPNsfdL>H^ov2cpJz^F{_PhxM89JHODfp8S|5K%iCI?c22C^SA#PC zYb3EIMp2w<$oyn{#@pH;y_~;zTbti5DbB{aw6w8NKN_xUV$q!9nV*b5^N!YHn5clF ze4-oMS>qiIS?I<{rWHi7%+@xxxvht=pN!=AVgB6)OzBdV*~A-*S)xz~v$C*>*ucyV)Mau$ zl0^=Mjz6y9PK+)$ny?x&QDx9SRIaqK(XXI>(mswq4(}K+iZzjPEj9L^X|Ew3twGP9 zi3g-2c<|LK8#_gj@yEskfOa*zLgN7d|K>eyWW&;pHWv9ln$QI!3;Zy@Z=)8i?|t3f zh)B4f72{*q$(wDgh#dG~{>nzJkKTyB+o;{&Thz=V)M0&wkk!?yW^}XSVK@6PDD!6{ zIDZ_j&sdt9GZsGfeKc=&)W$vqZ6^nQn6G^wBaR3q9`#@ERI*g5L53!&3H7A-uTV{o zqiio4A^Bs;j^_ZPrKH21!-^esJEpoemR@ROGeF70jgTi`r~QDU%N9VMN`46)!dUd9 zmHImjkHI#*6sVdHKty94u?f>y5mTo`<#q})t{>5O%57{n`Rb3uJ)RojqEa!`9 ze@UdWrOH}fs3Wh~=73bfIJCf?`}1B2*TACV_ba2q zDjUnXb+3$o@$q3pfX}UIQICl{OG5kc4*`cD*j12Ohic$jAvi5|b^&D80zb_AZPt3~ z)mtBvM5mF_gO$4Q1@BNh`_gA*rXS`XY}TUN#W%3Cr$IrNsb2Zv@K`%$UBOm+`Q14( zzr9&&ueXVIA0qox+<1Z!eJ?(Kn2-Jt-e)$&H^)K6y7!{zz@zLMGE9!{VZQZ48kTQ= zsP$}@*~QMj2F?3LL=LVm1hJYjxW1>uCu~8GMP2P|SvNZ?(H-FvOMcjRK-iWU>CyXU z?%jf})7jU~W`V2~MmG3ier=1^-l*-O*x`}j`hsz|oFeH%qQ)E@>?xXfJlTXX)6LdWJL`G^g$Xy{ z{J!m4&jzNYqs-1CK_9^qoWH(ZTXfel_mZ6zf|5(npfA~Zmrt}dqfM8`uONL_QR!eQ zes#q&aBLve`^e}AOufm_3HjB|=9_wA`y@esGOXv9NY-H3j{w0hzW)<#lF=)FO8eQv zK1EYEy=ziwXRSeIu;Gnu%ptwR(6N`w#YQKCF^@&Z1+cgqi*NwsMQo-l#%9VAY^E$_ z>2|zl98~$cQ33q1zD~q?FLY&aiY6XzcaY;L+<5kGkiu?&b|4+S9SW=aFX@;B`{|&! zkdEeFt!rm9*8dGr*d-7wL|)zDLAoZxs9 zWP0*%F>pjHtSRXKOy^XP0`=l%E%Q5$|6)z%Z;#5xETB*M|6)%T>Mq!ggB0tl@?-Pj>y~wxl zdG$1fd5#zrK$(sNgG~Hyfp&&RLqVc8-G*mFL&H%DQ=fJC9yD4UVbDJ&Nx%I4T^dQKR)2V27_FDPu@Ld55m z(T@BuAF@Xq6el8^g2;x~M^%4^Mt@-ai3Faz2BWVCJ{g$FH}BEh{e*U!NZP-&s)L(Q zYd+9x4m99nV-;IG!OS^be>SVnO5mOcf3C&y9-q^;!HCbbp54tlwrQWjwt-F{cLdk| zB?q5F6pBFXsLuJ_!hESla#55g=c`?kuK{i9;Gqp|!Oc*gJt zwv<6RFlU0?pwh>&XvcYpbnNA&+=Lyd_!HQJk{~a_{$5TTrWl|Up$964Oqi24VIjK~ z+e|q&4=dhwPcgkdrt#RDDaZOy>>r5@9kD(vo{j0mc*aUWNgy@_0YFwf4<6%~d=hv$ zDCIfmfZ{<(&%^F1=*@u{I%Xu?yAb*9AVj8Uy9Y7;6noH!btQ!E-M~B1&w}!yXvR2m z0k)PWptRv>&W{L8!9(XVr0!eDenUW&kpBlcf_of=$e9%A6d#2dI^rR+^A2>-xYFS+ z{xsZOhabPgt>*`T_h9*ok)@gAkh&)j>&x)$0z3ibS0GjJRAhJI3M_m;rN1G6IbseH$BYVdk-BB@d^tS34u>KDkh1_0fTx*oo3jeJ^(5RQ0pcWpr~_i# z+6{eC7fM4=EW2TkT*(i|16;UGsT9{>P?CiDTIY%R)U*R5hMfu2q-;ru0vPeV}{-(e`XPAUs!Z;O% zYl!j45;rIl4wB%BI15&O4grXiAjPFfK{4uuIPR1J57I&TB{&I(R&zgSG-w9sMbJ9X z7obwmFQD5XmsMkJLH$5af~J9TK(Bz__TX<9=oIKGNU>=w4%7!U5;O@k5A+gf3+O+f zDj1Xf5bN&~DJzpbC%{py8$+atf3VngLn_ zdL6VmK=ZKuFnkBP0tyP$SW{3B&=a6ppoO57pm#t!Kqo-wL4Si91ZgY*bRTFeXa;B* z=uMCpbP99{WN~OL9Mldp1oRjv6O`}pXlyGC2SDG0egoO+;DijQ2WSLn8Yl;}2DBS= z7IYI78VnCXX`tspt3V%s4uCF!B&Wt2f!cxkgNB2~gJyzW1g!$C^Wbkgs04Ht^czU3 zi*f^X1PujcfSv;_2fYpY1aus99#jnqtA~A3P$FnJ=xNYm&}PtKP#Ne3D7Zdy7nBGZ z1{w<*o{hidphD1xpk1INpr1g$gKQxhYXV9D4Frt8v9c(At#Xe)Zab4nb_5~|ud)Yo55IevoPkM6BjI4~d8Ivco zNBcfJaNy{%&rHdjK5oX0e$PzJnlyFhxLV=?Q!{Ew22P$iV`43tXVBD1S+!(B)PK_0 ztVz#I9W(vef3X}rCS%r^sbk084Pg4XF=0T#!L~}<`LOBX@)MM6igS9nKEu_ z#<+}rgQh+-X^xq_{uaY#X8pTO|NqTN|GQ5559*hS%*hxqHEX&z@R0VT=FRz9d)Z!3 z+>tPU1>$QArTlGN!@+f-|NkQp|KI)ygqYF&zwi+V?`vh+RQspp+Fr@}2QGv0tM_XS z_z&l_HkPJz$IBY`f6LU`n$v>rm4)R0Y1}Jw-ZFSRUQ{gZizU=8jyAsk5D!1a3b-FG z=8W;a^pp0d%|oAt2_Fbn=LXfw=y_kn8M9TInlJT^=ee}IzU zoj{c^3%(D;aB8R=TwMNf!t8emsKx~$aZzYsC_bnI9l;*}je=S5nV`uq3!Vd-2XnD7 zH^5wWw~F@y2?IzRQ7Qy~Cj!rdg%bGlk&KnWoCCf+3Ol+&4!*_Bn2LKmMNJsn6@!T- z%zhV-^b1H2HN&ZN$OV5Eln!$a_)yFzGhsF^7GXjxZXbzTNX=Sf>J78t%Rt+O9DE(9 z1ZMvWMB--An$DQ?LN55nASW*F6oX$)M1)}Wza%6s3q|%s4nZz>ENC*!@g5k4_Qt7y z0KXeQ`puv5eNY0B3qA|<1I&Ui0sRfL|3x5iA!zY^2>1(C=lfw!gt-*_wL!=UnEfyJ zR6w?2Fv>p%7Q(O>ve_X-|@11k*c}}52Vl|4}9Ca}_;#oi(jUssQ3KYeQ z*P#T$A0El|pB{Y*!)Dj(n()tDQK{UUvhgNxl*H+*U|rO)WU{#QYG6W~ zfSU2TGX5H^!|Nh*_%%+0bU)yBq|;UR2>ydMQAhU*?z@)L6|egRP186&@VZ}cMh#~Q zUiS^2L!s9=Q{ekG^nWfBx|uLwhO;8wR9J&lNjDdY?yv_6`v$|Oczu}e3%nWOXjdIr za+l-FURZ^UaG*>!ktphrQUolkR&P%ch>hQ_JRvi}8p6!1Bi*%8MqXPP?Vbtr)sr=jHQ z8vik${F~M>BL(klA$g4VgmmoiPM??9#p{XBz(FV%@5$xZd7bSqGtyBm>h8xx)q#hv zX1K+B;y8AE=jvDJ|0pxAeueQIHQ~h<(Mr6y+AUAO6B4wV@Y0sbxRp{Qst% z7BP{AB_DI9;B`N*25D<_|8IDT!G!WC-25rKm<&wzXU1f1qjB=^6_k(n1ZV6(&AT0Z z|4=SIwS)5>uiJfh?&RRz!1%BGeOK<%1X~6VhHOOhHGfV~E*79Ac-`$9mFBA%uN#q{ z@8$_5ylzZx`Ia#NubYiG?qM(Db@OqL>vd1@G^7pF4X~ue|Loxtj{D9T>2#~D1*tp% z*MILEbh`C6=?Au$_>FGK`MaD^(i7ORgFMqHFQATiA1cO2q3(*7;l*2#hHxu_nZT{m zDOxE!nC}sH2T8C{E?&;9)P(B5cTf_q+p;f&e5M_rfJgTBnZ5WhysWR!jGkFr2HzvaDXQ<#Abm6`6AjO9KM0*Cj8M1jeei+^p?>Qt2qzI7pmk~Zw zO(pTPGkvBGFP@9)@nvv&35~-iM*Ga*b13J)@g#xlgiv8AjiZiOgu3y(TgJRQk**@% z!)sj3-7{z$J_>nHj46JL@n7aiq`w2a2_YqiE_j#&lLf>{s20zQYRr_ly|Ce(FlO_| zJ~Kxj02g<$xA44t#*EtMtkaVzva?3_?dO1{j`-+)`oD{b7!zk7@R<-fBTtsd&K_kO zzZpuocncbf7w1!-RNV!((_Qj|n9yhrFi8_TtV_eiNpgSNNFLKa%#r z^Hvy!g^0zlm&S{5K7L^E%Rs zl5o^wwg6uSJDS)6d>TIXG`kWXgV#UfxBoJ(gvUP12JltOn~}_iZ&Oa4_!d%&ZGPt~ z7(TJzZ`!piy!DtKrW?Sw0*<@8hshx_#hZ)Fzg9b*w(U{)ag-JXC4^Ht*b6A3L*NBleL!bjln-v>;HFjFzyj>7mf?0*uC z#(NS~2h&!T9luiSY66-_C9xJ&;>Cq1f{(*HPIemPO=2mLweK`SKGg99qz*}3d6QL2 zT(ys&(bR7r!4Hz#nCL~uN!)WLF(tfs=~=WI?+I0z9>oDOb0j}nQ7+zecED8NE8$1v ztg7(djFqxi&y@sB9pz#RYQX=Jxw6>3-k_BNSGz}(kD-z{=p25M!;6d1UVI$BUP=ao zNRuZ^WoN7!&*vvN%EcB`iuXpR6tn8Sfa8O5(dsiUU?NP2(@_*3fzvLerSc+Deo9<% z5skx(t5G{X0b9sRb;`qoIVy1<$|ct%zHteA6z|DJS%-wE6rTF>a&{?|#II2`Ui=Bo z!h6C{hXkh-nA&&+za3IXJnYJV*@_p3pbmT?{FHwMp$ngeH8)zcjwq5h45g6NHI!)$hF(KZA z3h_~R>r8$H#YbSjd!2@c;SKk3n(=K}33Kk}gZVlR!@LLhotXMzSp6Vd&#@JO*FD5E z{^;2z++4R5uB+@V33y>DjFWx;K4%nlcHwWd-PAOM4@DYv+RwNdq1YR76 z+VJ8fs1xsrKOGX4A|hp(5I+#-l5!G9q5{15M^uFOM5FA;)ccRIm6VHLp(?z7ybkaS zcP(DOlCMW~c>U_#fpjI5hUfn?VCpF^gWo+t;O>2m|Ml!Tq(4By@JXcGEoMHa*#eGy ztq4wRV9W6O`F~wzxp?s_Ybh&Y zLJtibA9wmyKg|zZ5wQP?)$;79d1AP|UgtWnyp3T_dPUw-0V_tCjUXN&0xL&{WKks_|_Wu*oL8oT`ijce>>-csh zyPXfv;~bOG61*PoSnqm0;&H?(r(BPItXM_=YYX&r!#bq?OTv?0ar*4wV-wA+NRLk} zMY`~a!>s?}DbI%IZstJ4dlP3$sSSLWFQE@PNa1KygcmPGWAT^1%lTj7&hSRi6iw?! zl~fW3ZKZMeLU`@_Y_abB!L>*mkc87yG@3dQ_{A>1Uh$sXnVm-)^9=_i<>DmNZ84#T zMLs~fo0WpQ($2S>9yob@H$yC2tOrnjM%qJq2t|)s$a{lp3bZ|h^4dc-|8EcV@Oma> z5z?C@^eoYhNcRKutdaGn?>K*{qz8`XqB6W5KsxVxw;WDIIso<1(I-eDRg$v0_6l!?q(Iw^-%%On zremFrth6vakE0RH$c7{GGfWZQldZFJc*_Q7a7QF$#3YL1#lbwj6~l|yAeDRLb&B0R zc}j-aM!A?m<`WtOSD%_;tZXJ^jwe>98Q!>^Vs}5HV#>vw(=*Hjd>GzN>TWXLo3>L5 z?_Yn&F!LxE-$QYH3SLyob7lB)xM~ay!6(M>K-z(^8K#Q~b6$oyql^zpvGwq_3G@>_ z0>31#SBCe*^z8WFLQ;A)l#46T9K0umcSvGSNxfImChCZHmeYuo6|zk$6KQ57VJ3+@ z^C{f{52p9LDLtk2UPndL5&KrqIJ|fjD#usCC1mp|@t#bcoz`?ZtFid6+aIdadaKo zN4%Ssv+U@dH(sZh-2=qtDySqrifZuU+o%pNeunDt;x4z`8?RIBt{%myBQ8Vh@ZR*? zn{(KuM=l@-$qeya6xv4XVBtb`HC}95L`D!FhZ~y+is3y;Iy+&v{%QJ?a#2rI&clmC zpJ92t9>csBY1sZHRA(_mX}gP-a$e)b>1Z210{h2l96k&aD|r{!7c2~Cy_jJJ;G?kS zWe!Gs1cqK=ELqJl1pBwrP|Cd-JZ1BiY+x@^E?WEeUjq}K+?}1r3%<#LNx66oO5?o| zJ4NqKNRlo5GGy~I{4l&XDd&mN8O7<=zfBI2I%5AV?1?WK|7B*PIn0Q{*FJJuorKd# z&^1u*P0lG%SM(VNBjw^Gl*W5gaZ1ZA`-1M<&WH)y(J;Ix3}=Vr2JNsoJ(w|Q2WJ7Q z!g~X83d$Y6hXaXnF?TNq6rOkZnhhv{Pr_B-J6+%n!zm;;wu@7mI^yFfjrWG)s;qq( zX8w;1I2|FIZ{qXtNjSaR-FkS%Pb8ozuY|*XcKW^;7Wjg8-+ME1%FeCwlM|u7*t18_ zOvZb%adu{|cQ9zIYG%lcKy&cLa0k+EPs2r-c*^5&c280h_!vB+7m*nb%wpL4H$fAp zJR5FBIxtgkQ$MG1o{XKHy~`w0*Fk+~nG>1lX2P4hQ~qv9E-Ts*ve}9shEKw^{fUyu z!}&)CO*x*oB%42l$w%Qmi90)qH{y3}80BIGisSzmuw(pJ8t=;iLDR+zamaB&(}@=! zMdm9$0RFas_X6O;4@XGO?Jf5B1yJ1++L@5_% zTW7E$CT7ED&`P{FpQqejeNoV)C>NKby{ZG7&SY!W;9=w}5{qwerobSvJzdm#B71g} z@8%LxIy*x~ydM?gtth6~XjZg}1>m}KIdGWajqfSe*EBY0W>GG_f$H%|xcDzl-^bxS zoaO1^<4^~0Uww||EvFaskQmT~m$% zggd!e0dGb_@#4d%1RsOHz0U0>xB=PY$MqzsNcD}UjyKKM_hyR^CF)np3~@f1hZjFb zO?Yv?TkehUDav>6-#Ox`BR-ANc=0=Ac7<&I z5Eo8kYw+SrXaYU~=iSD6jE})tHBR40VfhTF@5Q}rkow*e+%pRFU49oKES42-L+kJn zXm#^H`&#a9VsJA#3z%VMaYo$d^j{jj9(DRS2^Y_H`Zx}ce8lP3F#H_p$~O&HKkl5C zo^YQX3_KVF6c^S9P5#%6|1vrA`LM6~9SEL|#^Q@%%acxDrr`Ppr=i|Jpn`(4p9-34 z>Wj-z9X<|g8#&lEa%{m1moR=XSb4&Lc6f06GIm!z3v6G;Fu9xs@ZyDN9bTN|mY2gf zo^$#y2@h`$ns(}V1A__@23y#mZ}{ef$Dw?DA$;^jx{ZqyPgu}wwkXVhiBp0~;vsbd=(Em8yR|fxvqSUW~O-NlV-qy+n zQ(glfLY8igL@@;$7$%t^K8_mj^{_X=#W+42mLt^{rxG+=MR_&sMqPOE1oDJq)B0N> zXl_LIjVhS;7H1merSK}G^;EpY`9I&Ch`~13i#uH333LAIEFd25`T|&o^kbB`$}Ja@ zt{3|!ovqJTa1=21OA8>sP?tGhaTHOirxA2X&mTrO-uAdBhz7sSF z>L?{NE% z@T4!D29?1mn!t);@C{UsPr~ofWW3oPG=D^u_)@qGRpaBZPlvP49QZe+jjZb6{4d?X zfyCeB;y9#TR1d#GYD6dO^_5dD7P!6)KI!@fn6cBT6M};}8Ls)@A~+Xm!{TtvF23R^ z9}5eXXGABw zWjEg`o7qa}`_}OR_z-HQeiT|m_BbC<0ROs|Enr3~yzV;&A~gu!^#kWLz83C9YJ~V$ zm*eNbp8MTB1f$&yTS% zr5v~nsXO9ubAYHID{6xqGRSb_H^GaE;pXGL5nM%czv;<3_k?Wr&dM|e_-t5(w1>p! zviPSJ)W@w%j3IKX8Dn8Bn#=;?Xkxmh`0+5C*KHJOMeq`&hE9YnuHOb5vz^8@!Hhmm zoe+GmZ>FhZ*(mHr`rK{{<0sOqaXtV?qZpM+;gv{RUjgrMeFUy_eFFZ7nyKFn`yOuZ zQj-JEM@uNLgUgZD(+tuiEQA=Nki*(-UOri(hEyiAK{e4X7Mg}wieE!fLx1Lqz^mK-00=O0ZVmcMSx z;7v$JekE){TIou7LfHARLUtWSEr(9f$)aW>T+j1w` z;4}G|_At2w?nhd<8;&{N@nhjkq=jd}q+2c?KG>;~3n#h096pEC&}P_gh|`E%xEJZS z086H%z^NqGB7Lyfg6!Q3cc5)-eJ4EO1gB0RywmlyFyVUfs1uz!VYnG}vTPg7`Mpyv z`U>g)!tXfp3purjduzxP|AewB9}DY{wm@t{YFru)I@$3hFp0)fU%cWJx(QzahYq7r zc=5C|*Z}-6c)@VTj~{MvO8gXyv`DeNZUkppeF`{i@rEnAxcI}}OI0Mb2P6U39n(*Cl@OZ`*EerpBA$ttJ5?W_p z?0i58JnyfWrh^$}@V!fDH5Zp#;g^?jWdFc!hrY`>c=)gYJhq(o4^ut>etZQ35k3Vk zyOJ0-b;@BHX;c-rRM0r;w86)&;q3Z>@qZqsj0Al-GsHNuPY<}s_2NC(XYxY>M?O4& z^b=jq4ZJ1*ss228;uHoS>J-A0Zge(mDBOV5h)u8qss3JAb(7_umpAdjNV{<*+^%UB z=zw`QXPOi%6|X==T^wBSVWeH!48wnC4^dtX8<9Ss3C3?>!zeGQ$}}(C>NH{-JYuRd zu9R4qPNW6I3DfwNV*znBQiBrk=4uWoF3l?89;EWU@S^E7LiOPRq^&e$@XOFT%Ed0! zhA+LH@0uDmhR+JX>B#>3zXtOmQX|^o#u;qQK6V$p`VMCgRm10yo(FG+gCl&)QKtZ2 zi%Rj6;qyp+->mXGopnxzWA1Vq?Me76`Oo=3lLLW;#p$RXUjvuUV=M4+c>3c`BZk4e zfAY;nx%enjH_d~0J>k@;g%8&AmI&%ZVK>qcE{>n?)Db8Ci;daG_%E|#0lSC=I^nA^ zceley3t0i><*;}$gVFo!M%a$@Yq_|)iAL?GJK+6K^F@WPgHIyu(FXXD>r?QOXPosn z!T#2>oXaeb2UjDFTnRX1iBqWoMwao%2zA7_k#=tzOe1}OxNNy|TE<}#X|ITRamR~O zTwnMc-TiO&I_tFBF=w~9AGR{n?0k`xGNbY(&S|6-rQqn7>5g``9{N`~yDfU7kU4Q?L}UzVSABRnb_W@>v=bcUL|6ei+Wp#)+wH@ zH+^VAluXkF65#=oxeARQQ&wAzkpm`Z>M=fX_YD3#lGwMXG$Z(aJ zh6$8JDHKN`%Dw;n>Lac&P${y|e)I`SqB67#y?~xVg&%SC_!=gPCZlZh?kBt;4Xr`X zqs3@8nt`rE7oy;&LGyDeXm+E|(K~1jT8bV=_n<333z|#OdFU*368atLhceMV*1=QS zbLv9bzPZ23&$^{=l;n7i|7}ZXPS3fI}T)()XmS-}rplEdx)^y4~js z&s}s%*5y6R`0IxCw=SG}L}AtdE4{pnJcTLeOIdA+g<{#U+*ml4A1jOvixtO8W0PZ* zvFaG}A{^!*vAlJ8Qp-d+_dse<$D+vMS&Ng6TN}HUn5C>kuO2t^7RDBuxKk(87;emO bY;9DXR3kIPWbQ$tW%bakQ9k~GzwG}99aTm{ diff --git a/src/ZstdSharp.Test/ZstdTest.cs b/src/ZstdSharp.Test/ZstdTest.cs index cd9b8e5..4449c1d 100644 --- a/src/ZstdSharp.Test/ZstdTest.cs +++ b/src/ZstdSharp.Test/ZstdTest.cs @@ -20,10 +20,12 @@ private Span CompressNative(byte[] srcBuffer, int compressBound, int level var cctx = ExternMethods.ZSTD_createCCtx(); try { - var length = ExternMethods.ZSTD_compressCCtx(cctx, - (IntPtr) bufferPtr, (nuint) buffer.Length, - (IntPtr) srcPtr, (nuint) srcBuffer.Length, + ExternMethods.ZSTD_CCtx_setParameter(cctx, ExternMethods.ZSTD_cParameter.ZSTD_c_compressionLevel, level); + + var length = ExternMethods.ZSTD_compress2(cctx, + (IntPtr) bufferPtr, (nuint) buffer.Length, + (IntPtr) srcPtr, (nuint) srcBuffer.Length); return new Span(buffer, 0, (int) length); } finally diff --git a/src/ZstdSharp/BitOperations.cs b/src/ZstdSharp/Polyfills/BitOperations.cs similarity index 93% rename from src/ZstdSharp/BitOperations.cs rename to src/ZstdSharp/Polyfills/BitOperations.cs index 90e1044..f5bb209 100644 --- a/src/ZstdSharp/BitOperations.cs +++ b/src/ZstdSharp/Polyfills/BitOperations.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if NETSTANDARD +#if !NETCOREAPP3_0_OR_GREATER using System.Runtime.CompilerServices; using static ZstdSharp.UnsafeHelper; @@ -16,9 +16,10 @@ namespace System.Numerics /// The methods use hardware intrinsics when available on the underlying platform, /// otherwise they use optimized software fallbacks. /// - public unsafe static class BitOperations + public static unsafe class BitOperations { - private static readonly byte* TrailingZeroCountDeBruijn = GetArrayPointer(new byte[] + // hack: should be public because of inline + public static readonly byte* TrailingZeroCountDeBruijn = GetArrayPointer(new byte[] { 00, 01, 28, 02, 29, 14, 24, 03, 30, 22, 20, 15, 25, 17, 04, 08, @@ -26,7 +27,8 @@ public unsafe static class BitOperations 26, 12, 18, 06, 11, 05, 10, 09 }); - private static readonly byte* Log2DeBruijn = GetArrayPointer(new byte[] + // hack: should be public because of inline + public static readonly byte* Log2DeBruijn = GetArrayPointer(new byte[] { 00, 09, 01, 10, 13, 21, 02, 29, 11, 14, 16, 18, 22, 25, 03, 30, @@ -40,7 +42,6 @@ public unsafe static class BitOperations /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static int Log2(uint value) { // The 0->0 contract is fulfilled by setting the LSB to 1. @@ -77,7 +78,6 @@ public static int Log2(uint value) /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static int Log2(ulong value) { value |= 1; @@ -98,7 +98,6 @@ public static int Log2(ulong value) /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static int TrailingZeroCount(int value) => TrailingZeroCount((uint)value); @@ -108,7 +107,6 @@ public static int TrailingZeroCount(int value) /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static int TrailingZeroCount(uint value) { // Unguarded fallback contract is 0->0, BSF contract is 0->undefined @@ -129,7 +127,6 @@ public static int TrailingZeroCount(uint value) /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static int TrailingZeroCount(long value) => TrailingZeroCount((ulong)value); @@ -139,7 +136,6 @@ public static int TrailingZeroCount(long value) /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static int TrailingZeroCount(ulong value) { uint lo = (uint)value; @@ -161,7 +157,6 @@ public static int TrailingZeroCount(ulong value) /// Any value outside the range [0..31] is treated as congruent mod 32. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static uint RotateLeft(uint value, int offset) => (value << offset) | (value >> (32 - offset)); @@ -174,7 +169,6 @@ public static uint RotateLeft(uint value, int offset) /// Any value outside the range [0..63] is treated as congruent mod 64. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static ulong RotateLeft(ulong value, int offset) => (value << offset) | (value >> (64 - offset)); @@ -187,7 +181,6 @@ public static ulong RotateLeft(ulong value, int offset) /// Any value outside the range [0..31] is treated as congruent mod 32. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static uint RotateRight(uint value, int offset) => (value >> offset) | (value << (32 - offset)); @@ -200,7 +193,6 @@ public static uint RotateRight(uint value, int offset) /// Any value outside the range [0..63] is treated as congruent mod 64. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static ulong RotateRight(ulong value, int offset) => (value >> offset) | (value << (64 - offset)); } diff --git a/src/ZstdSharp/SkipLocalsInitAttribute.cs b/src/ZstdSharp/Polyfills/SkipLocalsInitAttribute.cs similarity index 96% rename from src/ZstdSharp/SkipLocalsInitAttribute.cs rename to src/ZstdSharp/Polyfills/SkipLocalsInitAttribute.cs index 22b19a1..cb043f6 100644 --- a/src/ZstdSharp/SkipLocalsInitAttribute.cs +++ b/src/ZstdSharp/Polyfills/SkipLocalsInitAttribute.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NET5_0 +#if !NET5_0_OR_GREATER namespace System.Runtime.CompilerServices { ///

@@ -22,4 +22,4 @@ internal sealed class SkipLocalsInitAttribute : Attribute { } } -#endif \ No newline at end of file +#endif diff --git a/src/ZstdSharp/Polyfills/Unsafe.cs b/src/ZstdSharp/Polyfills/Unsafe.cs new file mode 100644 index 0000000..daf4219 --- /dev/null +++ b/src/ZstdSharp/Polyfills/Unsafe.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NETCOREAPP3_0_OR_GREATER + +using InlineIL; +using static InlineIL.IL.Emit; + +namespace System.Runtime.CompilerServices +{ + public static unsafe class Unsafe + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T As(object o) + where T : struct + { + Ldarg(nameof(o)); + return IL.Return(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo As(ref TFrom source) + { + Ldarg(nameof(source)); + return ref IL.ReturnRef(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SizeOf() + { + Sizeof(typeof(T)); + return IL.Return(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Add(ref T source, int elementOffset) + { + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Conv_I(); + Mul(); + IL.Emit.Add(); + return ref IL.ReturnRef(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SkipInit(out T value) + { + Ret(); + throw IL.Unreachable(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(void* source) + { + // For .NET Core the roundtrip via a local is no longer needed +#if NETCOREAPP + IL.Push(source); + return ref IL.ReturnRef(); +#else + // Roundtrip via a local to avoid type mismatch on return that the JIT inliner chokes on. + IL.DeclareLocals( + false, + new LocalVar("local", typeof(int).MakeByRefType()) + ); + + IL.Push(source); + Stloc("local"); + Ldloc("local"); + return ref IL.ReturnRef(); +#endif + } + } +} + +#endif diff --git a/src/ZstdSharp/Polyfills/Vector128.cs b/src/ZstdSharp/Polyfills/Vector128.cs new file mode 100644 index 0000000..3985df5 --- /dev/null +++ b/src/ZstdSharp/Polyfills/Vector128.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NETCOREAPP3_0_OR_GREATER + +using System.Runtime.CompilerServices; + +namespace System.Runtime.Intrinsics +{ + public static class Vector128 + { + internal const int Size = 16; + + public static unsafe Vector128 Create(byte value) + { + byte* pResult = stackalloc byte[16] + { + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + value, + }; + + return Unsafe.AsRef>(pResult); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 As(this Vector128 vector) + where T : struct + where U : struct => + Unsafe.As, Vector128>(ref vector); + + public static T GetElement(this Vector128 vector, int index) + where T : struct + { + ref T e0 = ref Unsafe.As, T>(ref vector); + return Unsafe.Add(ref e0, index); + } + } +} + +#endif diff --git a/src/ZstdSharp/Polyfills/Vector128_1.cs b/src/ZstdSharp/Polyfills/Vector128_1.cs new file mode 100644 index 0000000..659fdb9 --- /dev/null +++ b/src/ZstdSharp/Polyfills/Vector128_1.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NETCOREAPP3_0_OR_GREATER + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.Intrinsics +{ + [StructLayout(LayoutKind.Sequential, Size = Vector128.Size)] + public readonly struct Vector128 : IEquatable> + where T : struct + { + private readonly ulong _00; + private readonly ulong _01; + + public static int Count => Vector128.Size / Unsafe.SizeOf(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Vector128 other) + { + for (int i = 0; i < Count; i++) + { + if (!((IEquatable)(this.GetElement(i))).Equals(other.GetElement(i))) + { + return false; + } + } + + return true; + } + } +} + +#endif diff --git a/src/ZstdSharp/Unsafe/Arrays.cs b/src/ZstdSharp/Unsafe/Arrays.cs index bacbc5c..0d9bd0b 100644 --- a/src/ZstdSharp/Unsafe/Arrays.cs +++ b/src/ZstdSharp/Unsafe/Arrays.cs @@ -213,6 +213,13 @@ public static unsafe partial class Methods 42, 42, }); + static ulong* srcSizeTiers = GetArrayPointer(new ulong[4] + { + 16 * (1 << 10), + 128 * (1 << 10), + 256 * (1 << 10), + (unchecked(0UL - 1)), + }); static ZSTD_blockCompressor[][] blockCompressor = new ZSTD_blockCompressor[4][] { new ZSTD_blockCompressor[10] @@ -268,29 +275,66 @@ public static unsafe partial class Methods null, }, }; + static ZSTD_blockCompressor[][] rowBasedBlockCompressors = new ZSTD_blockCompressor[4][] + { + new ZSTD_blockCompressor[3] + { + ZSTD_compressBlock_greedy_row, + ZSTD_compressBlock_lazy_row, + ZSTD_compressBlock_lazy2_row, + }, + new ZSTD_blockCompressor[3] + { + ZSTD_compressBlock_greedy_extDict_row, + ZSTD_compressBlock_lazy_extDict_row, + ZSTD_compressBlock_lazy2_extDict_row, + }, + new ZSTD_blockCompressor[3] + { + ZSTD_compressBlock_greedy_dictMatchState_row, + ZSTD_compressBlock_lazy_dictMatchState_row, + ZSTD_compressBlock_lazy2_dictMatchState_row, + }, + new ZSTD_blockCompressor[3] + { + ZSTD_compressBlock_greedy_dedicatedDictSearch_row, + ZSTD_compressBlock_lazy_dedicatedDictSearch_row, + ZSTD_compressBlock_lazy2_dedicatedDictSearch_row, + }, + }; static searchMax_f[][] searchFuncs = new searchMax_f[4][] { - new searchMax_f[2] + new searchMax_f[3] { ZSTD_HcFindBestMatch_selectMLS, ZSTD_BtFindBestMatch_selectMLS, + ZSTD_RowFindBestMatch_selectRowLog, }, - new searchMax_f[2] + new searchMax_f[3] { null, null, + null, }, - new searchMax_f[2] + new searchMax_f[3] { ZSTD_HcFindBestMatch_dictMatchState_selectMLS, ZSTD_BtFindBestMatch_dictMatchState_selectMLS, + ZSTD_RowFindBestMatch_dictMatchState_selectRowLog, }, - new searchMax_f[2] + new searchMax_f[3] { ZSTD_HcFindBestMatch_dedicatedDictSearch_selectMLS, null, + ZSTD_RowFindBestMatch_dedicatedDictSearch_selectRowLog, }, }; + static searchMax_f[] searchFuncsExtGeneric = new searchMax_f[3] + { + ZSTD_HcFindBestMatch_extDict_selectMLS, + ZSTD_BtFindBestMatch_extDict_selectMLS, + ZSTD_RowFindBestMatch_extDict_selectRowLog, + }; static decompressionAlgo[] decompress = new decompressionAlgo[2] { HUF_decompress4X1, diff --git a/src/ZstdSharp/Unsafe/Bitstream.cs b/src/ZstdSharp/Unsafe/Bitstream.cs index f6925cf..9dfa339 100644 --- a/src/ZstdSharp/Unsafe/Bitstream.cs +++ b/src/ZstdSharp/Unsafe/Bitstream.cs @@ -84,7 +84,6 @@ private static nuint BIT_initCStream(BIT_CStream_t* bitC, void* startPtr, nuint * can add up to 31 bits into `bitC`. * Note : does not check for register overflow ! */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void BIT_addBits(BIT_CStream_t* bitC, nuint value, uint nbBits) { assert(nbBits < ((nuint)(sizeof(uint) * 32) / (nuint)(sizeof(uint)))); @@ -97,7 +96,6 @@ private static void BIT_addBits(BIT_CStream_t* bitC, nuint value, uint nbBits) * works only if `value` is _clean_, * meaning all high bits above nbBits are 0 */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void BIT_addBitsFast(BIT_CStream_t* bitC, nuint value, uint nbBits) { assert((value >> (int)nbBits) == 0); @@ -110,7 +108,6 @@ private static void BIT_addBitsFast(BIT_CStream_t* bitC, nuint value, uint nbBit * assumption : bitContainer has not overflowed * unsafe version; does not check buffer overflow */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void BIT_flushBitsFast(BIT_CStream_t* bitC) { nuint nbBytes = bitC->bitPos >> 3; @@ -129,7 +126,6 @@ private static void BIT_flushBitsFast(BIT_CStream_t* bitC) * note : does not signal buffer overflow. * overflow will be revealed later on using BIT_closeCStream() */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void BIT_flushBits(BIT_CStream_t* bitC) { nuint nbBytes = bitC->bitPos >> 3; @@ -276,7 +272,6 @@ private static nuint BIT_getUpperBits(nuint bitContainer, uint start) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint BIT_getMiddleBits(nuint bitContainer, uint start, uint nbBits) { uint regMask = (uint)((nuint)(sizeof(nuint)) * 8 - 1); @@ -299,7 +294,6 @@ private static nuint BIT_getLowerBits(nuint bitContainer, uint nbBits) * On 64-bits, maxNbBits==56. * @return : value extracted */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint BIT_lookBits(BIT_DStream_t* bitD, uint nbBits) { return BIT_getMiddleBits(bitD->bitContainer, (uint)(((nuint)(sizeof(nuint)) * 8) - bitD->bitsConsumed - nbBits), nbBits); @@ -308,7 +302,6 @@ private static nuint BIT_lookBits(BIT_DStream_t* bitD, uint nbBits) /*! BIT_lookBitsFast() : * unsafe version; only works if nbBits >= 1 */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint BIT_lookBitsFast(BIT_DStream_t* bitD, uint nbBits) { uint regMask = (uint)((nuint)(sizeof(nuint)) * 8 - 1); @@ -318,7 +311,6 @@ private static nuint BIT_lookBitsFast(BIT_DStream_t* bitD, uint nbBits) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void BIT_skipBits(BIT_DStream_t* bitD, uint nbBits) { bitD->bitsConsumed += nbBits; @@ -329,7 +321,6 @@ private static void BIT_skipBits(BIT_DStream_t* bitD, uint nbBits) * Pay attention to not read more than nbBits contained into local register. * @return : extracted value. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint BIT_readBits(BIT_DStream_t* bitD, uint nbBits) { nuint value = BIT_lookBits(bitD, nbBits); @@ -341,7 +332,6 @@ private static nuint BIT_readBits(BIT_DStream_t* bitD, uint nbBits) /*! BIT_readBitsFast() : * unsafe version; only works only if nbBits >= 1 */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint BIT_readBitsFast(BIT_DStream_t* bitD, uint nbBits) { nuint value = BIT_lookBitsFast(bitD, nbBits); @@ -358,7 +348,6 @@ private static nuint BIT_readBitsFast(BIT_DStream_t* bitD, uint nbBits) * point you must use BIT_reloadDStream() to reload. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static BIT_DStream_status BIT_reloadDStreamFast(BIT_DStream_t* bitD) { if ((bitD->ptr < bitD->limitPtr)) @@ -379,7 +368,6 @@ private static BIT_DStream_status BIT_reloadDStreamFast(BIT_DStream_t* bitD) * @return : status of `BIT_DStream_t` internal register. * when status == BIT_DStream_unfinished, internal register is filled with at least 25 or 57 bits */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) { if (bitD->bitsConsumed > ((nuint)(sizeof(nuint)) * 8)) diff --git a/src/ZstdSharp/Unsafe/EntropyCommon.cs b/src/ZstdSharp/Unsafe/EntropyCommon.cs index 4940dd5..567e990 100644 --- a/src/ZstdSharp/Unsafe/EntropyCommon.cs +++ b/src/ZstdSharp/Unsafe/EntropyCommon.cs @@ -283,9 +283,9 @@ public static nuint FSE_readNCount(short* normalizedCounter, uint* maxSVPtr, uin */ public static nuint HUF_readStats(byte* huffWeight, nuint hwSize, uint* rankStats, uint* nbSymbolsPtr, uint* tableLogPtr, void* src, nuint srcSize) { - uint* wksp = stackalloc uint[89]; + uint* wksp = stackalloc uint[218]; - return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, (void*)wksp, (nuint)(sizeof(uint) * 89), 0); + return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, (void*)wksp, (nuint)(sizeof(uint) * 218), 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ZstdSharp/Unsafe/ErrorPrivate.cs b/src/ZstdSharp/Unsafe/ErrorPrivate.cs index 73a9e29..50b9567 100644 --- a/src/ZstdSharp/Unsafe/ErrorPrivate.cs +++ b/src/ZstdSharp/Unsafe/ErrorPrivate.cs @@ -7,7 +7,6 @@ namespace ZstdSharp.Unsafe public static unsafe partial class Methods { [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static uint ERR_isError(nuint code) { return (((code > (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_maxCode))))) ? 1U : 0U); diff --git a/src/ZstdSharp/Unsafe/FSE_DecompressWksp.cs b/src/ZstdSharp/Unsafe/FSE_DecompressWksp.cs new file mode 100644 index 0000000..24ac57d --- /dev/null +++ b/src/ZstdSharp/Unsafe/FSE_DecompressWksp.cs @@ -0,0 +1,12 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public unsafe partial struct FSE_DecompressWksp + { + public fixed short ncount[256]; + + /* Dynamically sized */ + public fixed uint dtable[1]; + } +} diff --git a/src/ZstdSharp/Unsafe/Fse.cs b/src/ZstdSharp/Unsafe/Fse.cs index ec21843..565c5e6 100644 --- a/src/ZstdSharp/Unsafe/Fse.cs +++ b/src/ZstdSharp/Unsafe/Fse.cs @@ -38,7 +38,6 @@ private static void FSE_initCState2(FSE_CState_t* statePtr, uint* ct, uint symbo } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void FSE_encodeSymbol(BIT_CStream_t* bitC, FSE_CState_t* statePtr, uint symbol) { FSE_symbolCompressionTransform symbolTT = ((FSE_symbolCompressionTransform*)(statePtr->symbolTT))[symbol]; @@ -139,7 +138,6 @@ private static byte FSE_decodeSymbol(FSE_DState_t* DStatePtr, BIT_DStream_t* bit /*! FSE_decodeSymbolFast() : unsafe, only works if no symbol has a probability > 50% */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static byte FSE_decodeSymbolFast(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD) { FSE_decode_t DInfo = ((FSE_decode_t*)(DStatePtr->table))[DStatePtr->state]; diff --git a/src/ZstdSharp/Unsafe/FseCompress.cs b/src/ZstdSharp/Unsafe/FseCompress.cs index daf1532..f87fd23 100644 --- a/src/ZstdSharp/Unsafe/FseCompress.cs +++ b/src/ZstdSharp/Unsafe/FseCompress.cs @@ -335,6 +335,7 @@ public static void FSE_freeCTable(uint* ct) } /* provides the minimum logSize to safely represent a distribution */ + [InlineMethod.Inline] private static uint FSE_minTableLog(nuint srcSize, uint maxSymbolValue) { uint minBitsSrc = BIT_highbit32((uint)(srcSize)) + 1; diff --git a/src/ZstdSharp/Unsafe/FseDecompress.cs b/src/ZstdSharp/Unsafe/FseDecompress.cs index 0f4d552..4eb3f47 100644 --- a/src/ZstdSharp/Unsafe/FseDecompress.cs +++ b/src/ZstdSharp/Unsafe/FseDecompress.cs @@ -335,35 +335,44 @@ private static nuint FSE_decompress_wksp_body(void* dst, nuint dstCapacity, void { byte* istart = (byte*)(cSrc); byte* ip = istart; - short* counting = stackalloc short[256]; uint tableLog; uint maxSymbolValue = 255; - uint* dtable = (uint*)(workSpace); - nuint NCountLength = FSE_readNCount_bmi2((short*)counting, &maxSymbolValue, &tableLog, (void*)istart, cSrcSize, bmi2); + FSE_DecompressWksp* wksp = (FSE_DecompressWksp*)(workSpace); - if ((ERR_isError(NCountLength)) != 0) + if (wkspSize < (nuint)(sizeof(FSE_DecompressWksp))) { - return NCountLength; + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_GENERIC))); } - if (tableLog > maxLog) + { - return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); + nuint NCountLength = FSE_readNCount_bmi2((short*)wksp->ncount, &maxSymbolValue, &tableLog, (void*)istart, cSrcSize, bmi2); + + if ((ERR_isError(NCountLength)) != 0) + { + return NCountLength; + } + + if (tableLog > maxLog) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); + } + + assert(NCountLength <= cSrcSize); + ip += NCountLength; + cSrcSize -= NCountLength; } - assert(NCountLength <= cSrcSize); - ip += NCountLength; - cSrcSize -= NCountLength; - if ((((uint)((1 + (1 << (int)(tableLog)))) + ((((nuint)(sizeof(short)) * (maxSymbolValue + 1) + (1UL << (int)tableLog) + 8) + (nuint)(sizeof(uint)) - 1) / (nuint)(sizeof(uint)))) * (nuint)(sizeof(uint))) > wkspSize) + if ((((uint)((1 + (1 << (int)(tableLog)))) + ((((nuint)(sizeof(short)) * (maxSymbolValue + 1) + (1UL << (int)tableLog) + 8) + (nuint)(sizeof(uint)) - 1) / (nuint)(sizeof(uint))) + (uint)((255 + 1) / 2) + 1) * (nuint)(sizeof(uint))) > wkspSize) { return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); } - workSpace = dtable + (1 + (1 << (int)(tableLog))); - wkspSize -= ((uint)((1 + (1 << (int)(tableLog)))) * (nuint)(sizeof(uint))); + workSpace = wksp->dtable + (1 + (1 << (int)(tableLog))); + wkspSize -= (nuint)(sizeof(FSE_DecompressWksp)) + ((uint)((1 + (1 << (int)(tableLog)))) * (nuint)(sizeof(uint))); { - nuint _var_err__ = FSE_buildDTable_internal(dtable, (short*)counting, maxSymbolValue, tableLog, workSpace, wkspSize); + nuint _var_err__ = FSE_buildDTable_internal((uint*)wksp->dtable, (short*)wksp->ncount, maxSymbolValue, tableLog, workSpace, wkspSize); if ((ERR_isError(_var_err__)) != 0) { @@ -373,16 +382,16 @@ private static nuint FSE_decompress_wksp_body(void* dst, nuint dstCapacity, void { - void* ptr = (void*)dtable; + void* ptr = (void*)wksp->dtable; FSE_DTableHeader* DTableH = (FSE_DTableHeader*)(ptr); uint fastMode = DTableH->fastMode; if (fastMode != 0) { - return FSE_decompress_usingDTable_generic(dst, dstCapacity, (void*)ip, cSrcSize, dtable, 1); + return FSE_decompress_usingDTable_generic(dst, dstCapacity, (void*)ip, cSrcSize, (uint*)wksp->dtable, 1); } - return FSE_decompress_usingDTable_generic(dst, dstCapacity, (void*)ip, cSrcSize, dtable, 0); + return FSE_decompress_usingDTable_generic(dst, dstCapacity, (void*)ip, cSrcSize, (uint*)wksp->dtable, 0); } } @@ -429,9 +438,9 @@ public static nuint FSE_buildDTable(uint* dt, short* normalizedCounter, uint max */ public static nuint FSE_decompress(void* dst, nuint dstCapacity, void* cSrc, nuint cSrcSize) { - uint* wksp = stackalloc uint[5251]; + uint* wksp = stackalloc uint[5380]; - return FSE_decompress_wksp(dst, dstCapacity, cSrc, cSrcSize, (uint)((14 - 2)), (void*)wksp, (nuint)(sizeof(uint) * 5251)); + return FSE_decompress_wksp(dst, dstCapacity, cSrc, cSrcSize, (uint)((14 - 2)), (void*)wksp, (nuint)(sizeof(uint) * 5380)); } } } diff --git a/src/ZstdSharp/Unsafe/HUF_CompressWeightsWksp.cs b/src/ZstdSharp/Unsafe/HUF_CompressWeightsWksp.cs new file mode 100644 index 0000000..15ff08f --- /dev/null +++ b/src/ZstdSharp/Unsafe/HUF_CompressWeightsWksp.cs @@ -0,0 +1,15 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public unsafe partial struct HUF_CompressWeightsWksp + { + public fixed uint CTable[59]; + + public fixed uint scratchBuffer[30]; + + public fixed uint count[13]; + + public fixed short norm[13]; + } +} diff --git a/src/ZstdSharp/Unsafe/HUF_ReadDTableX1_Workspace.cs b/src/ZstdSharp/Unsafe/HUF_ReadDTableX1_Workspace.cs index 3a6b7d0..74028de 100644 --- a/src/ZstdSharp/Unsafe/HUF_ReadDTableX1_Workspace.cs +++ b/src/ZstdSharp/Unsafe/HUF_ReadDTableX1_Workspace.cs @@ -8,7 +8,7 @@ public unsafe partial struct HUF_ReadDTableX1_Workspace public fixed uint rankStart[16]; - public fixed uint statsWksp[89]; + public fixed uint statsWksp[218]; public fixed byte symbols[256]; diff --git a/src/ZstdSharp/Unsafe/HUF_ReadDTableX2_Workspace.cs b/src/ZstdSharp/Unsafe/HUF_ReadDTableX2_Workspace.cs new file mode 100644 index 0000000..9924777 --- /dev/null +++ b/src/ZstdSharp/Unsafe/HUF_ReadDTableX2_Workspace.cs @@ -0,0 +1,389 @@ +using InlineIL; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; + +namespace ZstdSharp.Unsafe +{ + public unsafe partial struct HUF_ReadDTableX2_Workspace + { + public _rankVal_e__FixedBuffer rankVal; + + public fixed uint rankStats[13]; + + public fixed uint rankStart0[14]; + + public _sortedSymbol_e__FixedBuffer sortedSymbol; + + public fixed byte weightList[256]; + + public fixed uint calleeWksp[218]; + + public unsafe partial struct _rankVal_e__FixedBuffer + { + public rankValCol_t e0; + public rankValCol_t e1; + public rankValCol_t e2; + public rankValCol_t e3; + public rankValCol_t e4; + public rankValCol_t e5; + public rankValCol_t e6; + public rankValCol_t e7; + public rankValCol_t e8; + public rankValCol_t e9; + public rankValCol_t e10; + public rankValCol_t e11; + + public ref rankValCol_t this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + get => ref *(this + (uint)index); + } + + public ref rankValCol_t this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + get => ref *(this + index); + } + + public ref rankValCol_t this[nuint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + get => ref *(this + (uint)index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + public static implicit operator rankValCol_t*(in _rankVal_e__FixedBuffer t) + { + Ldarg_0(); + Ldflda(new FieldRef(typeof(_rankVal_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + public static rankValCol_t* operator +(in _rankVal_e__FixedBuffer t, uint index) + { + Ldarg_0(); + Ldflda(new FieldRef(typeof(_rankVal_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); + } + } + + public unsafe partial struct _sortedSymbol_e__FixedBuffer + { + public sortedSymbol_t e0; + public sortedSymbol_t e1; + public sortedSymbol_t e2; + public sortedSymbol_t e3; + public sortedSymbol_t e4; + public sortedSymbol_t e5; + public sortedSymbol_t e6; + public sortedSymbol_t e7; + public sortedSymbol_t e8; + public sortedSymbol_t e9; + public sortedSymbol_t e10; + public sortedSymbol_t e11; + public sortedSymbol_t e12; + public sortedSymbol_t e13; + public sortedSymbol_t e14; + public sortedSymbol_t e15; + public sortedSymbol_t e16; + public sortedSymbol_t e17; + public sortedSymbol_t e18; + public sortedSymbol_t e19; + public sortedSymbol_t e20; + public sortedSymbol_t e21; + public sortedSymbol_t e22; + public sortedSymbol_t e23; + public sortedSymbol_t e24; + public sortedSymbol_t e25; + public sortedSymbol_t e26; + public sortedSymbol_t e27; + public sortedSymbol_t e28; + public sortedSymbol_t e29; + public sortedSymbol_t e30; + public sortedSymbol_t e31; + public sortedSymbol_t e32; + public sortedSymbol_t e33; + public sortedSymbol_t e34; + public sortedSymbol_t e35; + public sortedSymbol_t e36; + public sortedSymbol_t e37; + public sortedSymbol_t e38; + public sortedSymbol_t e39; + public sortedSymbol_t e40; + public sortedSymbol_t e41; + public sortedSymbol_t e42; + public sortedSymbol_t e43; + public sortedSymbol_t e44; + public sortedSymbol_t e45; + public sortedSymbol_t e46; + public sortedSymbol_t e47; + public sortedSymbol_t e48; + public sortedSymbol_t e49; + public sortedSymbol_t e50; + public sortedSymbol_t e51; + public sortedSymbol_t e52; + public sortedSymbol_t e53; + public sortedSymbol_t e54; + public sortedSymbol_t e55; + public sortedSymbol_t e56; + public sortedSymbol_t e57; + public sortedSymbol_t e58; + public sortedSymbol_t e59; + public sortedSymbol_t e60; + public sortedSymbol_t e61; + public sortedSymbol_t e62; + public sortedSymbol_t e63; + public sortedSymbol_t e64; + public sortedSymbol_t e65; + public sortedSymbol_t e66; + public sortedSymbol_t e67; + public sortedSymbol_t e68; + public sortedSymbol_t e69; + public sortedSymbol_t e70; + public sortedSymbol_t e71; + public sortedSymbol_t e72; + public sortedSymbol_t e73; + public sortedSymbol_t e74; + public sortedSymbol_t e75; + public sortedSymbol_t e76; + public sortedSymbol_t e77; + public sortedSymbol_t e78; + public sortedSymbol_t e79; + public sortedSymbol_t e80; + public sortedSymbol_t e81; + public sortedSymbol_t e82; + public sortedSymbol_t e83; + public sortedSymbol_t e84; + public sortedSymbol_t e85; + public sortedSymbol_t e86; + public sortedSymbol_t e87; + public sortedSymbol_t e88; + public sortedSymbol_t e89; + public sortedSymbol_t e90; + public sortedSymbol_t e91; + public sortedSymbol_t e92; + public sortedSymbol_t e93; + public sortedSymbol_t e94; + public sortedSymbol_t e95; + public sortedSymbol_t e96; + public sortedSymbol_t e97; + public sortedSymbol_t e98; + public sortedSymbol_t e99; + public sortedSymbol_t e100; + public sortedSymbol_t e101; + public sortedSymbol_t e102; + public sortedSymbol_t e103; + public sortedSymbol_t e104; + public sortedSymbol_t e105; + public sortedSymbol_t e106; + public sortedSymbol_t e107; + public sortedSymbol_t e108; + public sortedSymbol_t e109; + public sortedSymbol_t e110; + public sortedSymbol_t e111; + public sortedSymbol_t e112; + public sortedSymbol_t e113; + public sortedSymbol_t e114; + public sortedSymbol_t e115; + public sortedSymbol_t e116; + public sortedSymbol_t e117; + public sortedSymbol_t e118; + public sortedSymbol_t e119; + public sortedSymbol_t e120; + public sortedSymbol_t e121; + public sortedSymbol_t e122; + public sortedSymbol_t e123; + public sortedSymbol_t e124; + public sortedSymbol_t e125; + public sortedSymbol_t e126; + public sortedSymbol_t e127; + public sortedSymbol_t e128; + public sortedSymbol_t e129; + public sortedSymbol_t e130; + public sortedSymbol_t e131; + public sortedSymbol_t e132; + public sortedSymbol_t e133; + public sortedSymbol_t e134; + public sortedSymbol_t e135; + public sortedSymbol_t e136; + public sortedSymbol_t e137; + public sortedSymbol_t e138; + public sortedSymbol_t e139; + public sortedSymbol_t e140; + public sortedSymbol_t e141; + public sortedSymbol_t e142; + public sortedSymbol_t e143; + public sortedSymbol_t e144; + public sortedSymbol_t e145; + public sortedSymbol_t e146; + public sortedSymbol_t e147; + public sortedSymbol_t e148; + public sortedSymbol_t e149; + public sortedSymbol_t e150; + public sortedSymbol_t e151; + public sortedSymbol_t e152; + public sortedSymbol_t e153; + public sortedSymbol_t e154; + public sortedSymbol_t e155; + public sortedSymbol_t e156; + public sortedSymbol_t e157; + public sortedSymbol_t e158; + public sortedSymbol_t e159; + public sortedSymbol_t e160; + public sortedSymbol_t e161; + public sortedSymbol_t e162; + public sortedSymbol_t e163; + public sortedSymbol_t e164; + public sortedSymbol_t e165; + public sortedSymbol_t e166; + public sortedSymbol_t e167; + public sortedSymbol_t e168; + public sortedSymbol_t e169; + public sortedSymbol_t e170; + public sortedSymbol_t e171; + public sortedSymbol_t e172; + public sortedSymbol_t e173; + public sortedSymbol_t e174; + public sortedSymbol_t e175; + public sortedSymbol_t e176; + public sortedSymbol_t e177; + public sortedSymbol_t e178; + public sortedSymbol_t e179; + public sortedSymbol_t e180; + public sortedSymbol_t e181; + public sortedSymbol_t e182; + public sortedSymbol_t e183; + public sortedSymbol_t e184; + public sortedSymbol_t e185; + public sortedSymbol_t e186; + public sortedSymbol_t e187; + public sortedSymbol_t e188; + public sortedSymbol_t e189; + public sortedSymbol_t e190; + public sortedSymbol_t e191; + public sortedSymbol_t e192; + public sortedSymbol_t e193; + public sortedSymbol_t e194; + public sortedSymbol_t e195; + public sortedSymbol_t e196; + public sortedSymbol_t e197; + public sortedSymbol_t e198; + public sortedSymbol_t e199; + public sortedSymbol_t e200; + public sortedSymbol_t e201; + public sortedSymbol_t e202; + public sortedSymbol_t e203; + public sortedSymbol_t e204; + public sortedSymbol_t e205; + public sortedSymbol_t e206; + public sortedSymbol_t e207; + public sortedSymbol_t e208; + public sortedSymbol_t e209; + public sortedSymbol_t e210; + public sortedSymbol_t e211; + public sortedSymbol_t e212; + public sortedSymbol_t e213; + public sortedSymbol_t e214; + public sortedSymbol_t e215; + public sortedSymbol_t e216; + public sortedSymbol_t e217; + public sortedSymbol_t e218; + public sortedSymbol_t e219; + public sortedSymbol_t e220; + public sortedSymbol_t e221; + public sortedSymbol_t e222; + public sortedSymbol_t e223; + public sortedSymbol_t e224; + public sortedSymbol_t e225; + public sortedSymbol_t e226; + public sortedSymbol_t e227; + public sortedSymbol_t e228; + public sortedSymbol_t e229; + public sortedSymbol_t e230; + public sortedSymbol_t e231; + public sortedSymbol_t e232; + public sortedSymbol_t e233; + public sortedSymbol_t e234; + public sortedSymbol_t e235; + public sortedSymbol_t e236; + public sortedSymbol_t e237; + public sortedSymbol_t e238; + public sortedSymbol_t e239; + public sortedSymbol_t e240; + public sortedSymbol_t e241; + public sortedSymbol_t e242; + public sortedSymbol_t e243; + public sortedSymbol_t e244; + public sortedSymbol_t e245; + public sortedSymbol_t e246; + public sortedSymbol_t e247; + public sortedSymbol_t e248; + public sortedSymbol_t e249; + public sortedSymbol_t e250; + public sortedSymbol_t e251; + public sortedSymbol_t e252; + public sortedSymbol_t e253; + public sortedSymbol_t e254; + public sortedSymbol_t e255; + + public ref sortedSymbol_t this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + get => ref *(this + (uint)index); + } + + public ref sortedSymbol_t this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + get => ref *(this + index); + } + + public ref sortedSymbol_t this[nuint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + get => ref *(this + (uint)index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + public static implicit operator sortedSymbol_t*(in _sortedSymbol_e__FixedBuffer t) + { + Ldarg_0(); + Ldflda(new FieldRef(typeof(_sortedSymbol_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + public static sortedSymbol_t* operator +(in _sortedSymbol_e__FixedBuffer t, uint index) + { + Ldarg_0(); + Ldflda(new FieldRef(typeof(_sortedSymbol_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); + } + } + } +} diff --git a/src/ZstdSharp/Unsafe/HUF_WriteCTableWksp.cs b/src/ZstdSharp/Unsafe/HUF_WriteCTableWksp.cs new file mode 100644 index 0000000..c217435 --- /dev/null +++ b/src/ZstdSharp/Unsafe/HUF_WriteCTableWksp.cs @@ -0,0 +1,14 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public unsafe partial struct HUF_WriteCTableWksp + { + public HUF_CompressWeightsWksp wksp; + + /* precomputed conversion table */ + public fixed byte bitsToWeight[13]; + + public fixed byte huffWeight[255]; + } +} diff --git a/src/ZstdSharp/Unsafe/HUF_buildCTable_wksp_tables.cs b/src/ZstdSharp/Unsafe/HUF_buildCTable_wksp_tables.cs index 44d8889..c86bd15 100644 --- a/src/ZstdSharp/Unsafe/HUF_buildCTable_wksp_tables.cs +++ b/src/ZstdSharp/Unsafe/HUF_buildCTable_wksp_tables.cs @@ -1,6 +1,8 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { @@ -529,48 +531,45 @@ public ref nodeElt_s this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 512); - public ref nodeElt_s this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref nodeElt_s this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator nodeElt_s*(in _huffNodeTbl_e__FixedBuffer t) { - fixed (nodeElt_s *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_huffNodeTbl_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static nodeElt_s* operator +(in _huffNodeTbl_e__FixedBuffer t, uint index) { - fixed (nodeElt_s *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_huffNodeTbl_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } @@ -613,48 +612,45 @@ public ref rankPos this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 32); - public ref rankPos this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref rankPos this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator rankPos*(in _rankPosition_e__FixedBuffer t) { - fixed (rankPos *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_rankPosition_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static rankPos* operator +(in _rankPosition_e__FixedBuffer t, uint index) { - fixed (rankPos *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_rankPosition_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/HUF_compress_tables_t.cs b/src/ZstdSharp/Unsafe/HUF_compress_tables_t.cs index 4f76243..fc68c09 100644 --- a/src/ZstdSharp/Unsafe/HUF_compress_tables_t.cs +++ b/src/ZstdSharp/Unsafe/HUF_compress_tables_t.cs @@ -1,6 +1,8 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { @@ -10,7 +12,7 @@ public unsafe partial struct HUF_compress_tables_t public _CTable_e__FixedBuffer CTable; - public HUF_buildCTable_wksp_tables buildCTable_wksp; + public _wksps_e__Union wksps; public unsafe partial struct _CTable_e__FixedBuffer { @@ -275,48 +277,45 @@ public ref HUF_CElt_s this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 256); - public ref HUF_CElt_s this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref HUF_CElt_s this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator HUF_CElt_s*(in _CTable_e__FixedBuffer t) { - fixed (HUF_CElt_s *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_CTable_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static HUF_CElt_s* operator +(in _CTable_e__FixedBuffer t, uint index) { - fixed (HUF_CElt_s *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_CTable_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/HufCompress.cs b/src/ZstdSharp/Unsafe/HufCompress.cs index 49f4e9e..2f37a5c 100644 --- a/src/ZstdSharp/Unsafe/HufCompress.cs +++ b/src/ZstdSharp/Unsafe/HufCompress.cs @@ -14,17 +14,19 @@ public static uint HUF_optimalTableLog(uint maxTableLog, nuint srcSize, uint max return FSE_optimalTableLog_internal(maxTableLog, srcSize, maxSymbolValue, 1); } - private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightTable, nuint wtSize) + private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightTable, nuint wtSize, void* workspace, nuint workspaceSize) { byte* ostart = (byte*)(dst); byte* op = ostart; byte* oend = ostart + dstSize; uint maxSymbolValue = 12; uint tableLog = 6; - uint* CTable = stackalloc uint[59]; - uint* scratchBuffer = stackalloc uint[30]; - uint* count = stackalloc uint[13]; - short* norm = stackalloc short[13]; + HUF_CompressWeightsWksp* wksp = (HUF_CompressWeightsWksp*)(workspace); + + if (workspaceSize < (nuint)(sizeof(HUF_CompressWeightsWksp))) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_GENERIC))); + } if (wtSize <= 1) { @@ -33,7 +35,7 @@ private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightT { - uint maxCount = HIST_count_simple((uint*)count, &maxSymbolValue, weightTable, wtSize); + uint maxCount = HIST_count_simple((uint*)wksp->count, &maxSymbolValue, weightTable, wtSize); if (maxCount == wtSize) { @@ -49,7 +51,7 @@ private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightT tableLog = FSE_optimalTableLog(tableLog, wtSize, maxSymbolValue); { - nuint _var_err__ = FSE_normalizeCount((short*)norm, tableLog, (uint*)count, wtSize, maxSymbolValue, 0); + nuint _var_err__ = FSE_normalizeCount((short*)wksp->norm, tableLog, (uint*)wksp->count, wtSize, maxSymbolValue, 0); if ((ERR_isError(_var_err__)) != 0) { @@ -59,7 +61,7 @@ private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightT { - nuint hSize = FSE_writeNCount((void*)op, (nuint)(oend - op), (short*)norm, maxSymbolValue, tableLog); + nuint hSize = FSE_writeNCount((void*)op, (nuint)(oend - op), (short*)wksp->norm, maxSymbolValue, tableLog); if ((ERR_isError(hSize)) != 0) { @@ -71,7 +73,7 @@ private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightT { - nuint _var_err__ = FSE_buildCTable_wksp((uint*)CTable, (short*)norm, maxSymbolValue, tableLog, (void*)scratchBuffer, (nuint)(120)); + nuint _var_err__ = FSE_buildCTable_wksp((uint*)wksp->CTable, (short*)wksp->norm, maxSymbolValue, tableLog, (void*)wksp->scratchBuffer, (nuint)(120)); if ((ERR_isError(_var_err__)) != 0) { @@ -81,7 +83,7 @@ private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightT { - nuint cSize = FSE_compress_usingCTable((void*)op, (nuint)(oend - op), weightTable, wtSize, (uint*)CTable); + nuint cSize = FSE_compress_usingCTable((void*)op, (nuint)(oend - op), weightTable, wtSize, (uint*)wksp->CTable); if ((ERR_isError(cSize)) != 0) { @@ -99,35 +101,36 @@ private static nuint HUF_compressWeights(void* dst, nuint dstSize, void* weightT return (nuint)(op - ostart); } - /*! HUF_writeCTable() : - `CTable` : Huffman tree to save, using huf representation. - @return : size of saved CTable */ - public static nuint HUF_writeCTable(void* dst, nuint maxDstSize, HUF_CElt_s* CTable, uint maxSymbolValue, uint huffLog) + public static nuint HUF_writeCTable_wksp(void* dst, nuint maxDstSize, HUF_CElt_s* CTable, uint maxSymbolValue, uint huffLog, void* workspace, nuint workspaceSize) { - byte* bitsToWeight = stackalloc byte[13]; - byte* huffWeight = stackalloc byte[255]; byte* op = (byte*)(dst); uint n; + HUF_WriteCTableWksp* wksp = (HUF_WriteCTableWksp*)(workspace); + + if (workspaceSize < (nuint)(sizeof(HUF_WriteCTableWksp))) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_GENERIC))); + } if (maxSymbolValue > 255) { return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_maxSymbolValue_tooLarge))); } - bitsToWeight[0] = 0; + wksp->bitsToWeight[0] = 0; for (n = 1; n < huffLog + 1; n++) { - bitsToWeight[n] = (byte)(huffLog + 1 - n); + wksp->bitsToWeight[n] = (byte)(huffLog + 1 - n); } for (n = 0; n < maxSymbolValue; n++) { - huffWeight[n] = bitsToWeight[CTable[n].nbBits]; + wksp->huffWeight[n] = wksp->bitsToWeight[CTable[n].nbBits]; } { - nuint hSize = HUF_compressWeights((void*)(op + 1), maxDstSize - 1, (void*)huffWeight, maxSymbolValue); + nuint hSize = HUF_compressWeights((void*)(op + 1), maxDstSize - 1, (void*)wksp->huffWeight, maxSymbolValue, (void*)&wksp->wksp, (nuint)(436)); if ((ERR_isError(hSize)) != 0) { @@ -152,15 +155,25 @@ public static nuint HUF_writeCTable(void* dst, nuint maxDstSize, HUF_CElt_s* CTa } op[0] = (byte)(128 + (maxSymbolValue - 1)); - huffWeight[maxSymbolValue] = 0; + wksp->huffWeight[maxSymbolValue] = 0; for (n = 0; n < maxSymbolValue; n += 2) { - op[(n / 2) + 1] = (byte)((huffWeight[n] << 4) + huffWeight[n + 1]); + op[(n / 2) + 1] = (byte)((wksp->huffWeight[n] << 4) + wksp->huffWeight[n + 1]); } return ((maxSymbolValue + 1) / 2) + 1; } + /*! HUF_writeCTable() : + `CTable` : Huffman tree to save, using huf representation. + @return : size of saved CTable */ + public static nuint HUF_writeCTable(void* dst, nuint maxDstSize, HUF_CElt_s* CTable, uint maxSymbolValue, uint huffLog) + { + HUF_WriteCTableWksp wksp; + + return HUF_writeCTable_wksp(dst, maxDstSize, CTable, maxSymbolValue, huffLog, (void*)&wksp, (nuint)(sizeof(HUF_WriteCTableWksp))); + } + /** HUF_readCTable() : * Loading a CTable saved with HUF_writeCTable() */ public static nuint HUF_readCTable(HUF_CElt_s* CTable, uint* maxSymbolValuePtr, void* src, nuint srcSize, uint* hasZeroWeights) @@ -653,14 +666,12 @@ public static nuint HUF_compressBound(nuint size) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void HUF_encodeSymbol(BIT_CStream_t* bitCPtr, uint symbol, HUF_CElt_s* CTable) { BIT_addBitsFast(bitCPtr, CTable[symbol].val, CTable[symbol].nbBits); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint HUF_compress1X_usingCTable_internal_body(void* dst, nuint dstSize, void* src, nuint srcSize, HUF_CElt_s* CTable) { byte* ip = (byte*)(src); @@ -1003,7 +1014,7 @@ private static nuint HUF_compress_internal(void* dst, nuint dstSize, void* src, huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); { - nuint maxBits = HUF_buildCTable_wksp((HUF_CElt_s*)table->CTable, (uint*)table->count, maxSymbolValue, huffLog, (void*)&table->buildCTable_wksp, (nuint)(4352)); + nuint maxBits = HUF_buildCTable_wksp((HUF_CElt_s*)table->CTable, (uint*)table->count, maxSymbolValue, huffLog, (void*)&table->wksps.buildCTable_wksp, (nuint)(4352)); { @@ -1021,7 +1032,7 @@ private static nuint HUF_compress_internal(void* dst, nuint dstSize, void* src, { - nuint hSize = HUF_writeCTable((void*)op, dstSize, (HUF_CElt_s*)table->CTable, maxSymbolValue, huffLog); + nuint hSize = HUF_writeCTable_wksp((void*)op, dstSize, (HUF_CElt_s*)table->CTable, maxSymbolValue, huffLog, (void*)&table->wksps.writeCTable_wksp, (nuint)(704)); if ((ERR_isError(hSize)) != 0) { diff --git a/src/ZstdSharp/Unsafe/HufDecompress.cs b/src/ZstdSharp/Unsafe/HufDecompress.cs index fcfc6f3..8b32644 100644 --- a/src/ZstdSharp/Unsafe/HufDecompress.cs +++ b/src/ZstdSharp/Unsafe/HufDecompress.cs @@ -55,7 +55,7 @@ public static nuint HUF_readDTableX1_wksp_bmi2(uint* DTable, void* src, nuint sr return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); } - iSize = HUF_readStats_wksp((byte*)wksp->huffWeight, (nuint)(255 + 1), (uint*)wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, (void*)wksp->statsWksp, (nuint)(sizeof(uint) * 89), bmi2); + iSize = HUF_readStats_wksp((byte*)wksp->huffWeight, (nuint)(255 + 1), (uint*)wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, (void*)wksp->statsWksp, (nuint)(sizeof(uint) * 218), bmi2); if ((ERR_isError(iSize)) != 0) { return iSize; @@ -212,7 +212,6 @@ public static nuint HUF_readDTableX1_wksp_bmi2(uint* DTable, void* src, nuint sr } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static byte HUF_decodeSymbolX1(BIT_DStream_t* Dstream, HUF_DEltX1* dt, uint dtLog) { nuint val = BIT_lookBitsFast(Dstream, dtLog); @@ -594,12 +593,14 @@ public static nuint HUF_decompress4X1_DCtx_wksp(uint* dctx, void* dst, nuint dst /* HUF_fillDTableX2Level2() : * `rankValOrigin` must be a table of at least (HUF_TABLELOG_MAX + 1) U32 */ - private static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, uint sizeLog, uint consumed, uint* rankValOrigin, int minWeight, sortedSymbol_t* sortedSymbols, uint sortedListSize, uint nbBitsBaseline, ushort baseSeq) + [InlineMethod.Inline] + private static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, uint sizeLog, uint consumed, uint* rankValOrigin, int minWeight, sortedSymbol_t* sortedSymbols, uint sortedListSize, uint nbBitsBaseline, ushort baseSeq, uint* wksp, nuint wkspSize) { HUF_DEltX2 DElt; - uint* rankVal = stackalloc uint[13]; + uint* rankVal = wksp; - memcpy((void*)(rankVal), (void*)(rankValOrigin), ((nuint)(sizeof(uint) * 13))); + assert(wkspSize >= (uint)(12 + 1)); + memcpy((void*)(rankVal), (void*)(rankValOrigin), ((nuint)(sizeof(uint)) * (uint)((12 + 1)))); if (minWeight > 1) { uint i, skipSize = rankVal[minWeight]; @@ -641,14 +642,17 @@ private static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, uint sizeLog, uin } } - private static void HUF_fillDTableX2(HUF_DEltX2* DTable, uint targetLog, sortedSymbol_t* sortedList, uint sortedListSize, uint* rankStart, rankValCol_t* rankValOrigin, uint maxWeight, uint nbBitsBaseline) + private static void HUF_fillDTableX2(HUF_DEltX2* DTable, uint targetLog, sortedSymbol_t* sortedList, uint sortedListSize, uint* rankStart, rankValCol_t* rankValOrigin, uint maxWeight, uint nbBitsBaseline, uint* wksp, nuint wkspSize) { - uint* rankVal = stackalloc uint[13]; + uint* rankVal = wksp; int scaleLog = (int)(nbBitsBaseline - targetLog); uint minBits = nbBitsBaseline - maxWeight; uint s; - memcpy((void*)(rankVal), (void*)(rankValOrigin), ((nuint)(sizeof(uint) * 13))); + assert(wkspSize >= (uint)(12 + 1)); + wksp += 12 + 1; + wkspSize -= (nuint)(12 + 1); + memcpy((void*)(rankVal), (void*)(rankValOrigin), ((nuint)(sizeof(uint)) * (uint)((12 + 1)))); for (s = 0; s < sortedListSize; s++) { ushort symbol = sortedList[s].symbol; @@ -668,7 +672,7 @@ private static void HUF_fillDTableX2(HUF_DEltX2* DTable, uint targetLog, sortedS } sortedRank = rankStart[minWeight]; - HUF_fillDTableX2Level2(DTable + start, targetLog - nbBits, nbBits, (uint*)(rankValOrigin[nbBits]), minWeight, sortedList + sortedRank, sortedListSize - sortedRank, nbBitsBaseline, symbol); + HUF_fillDTableX2Level2(DTable + start, targetLog - nbBits, nbBits, (uint*)(rankValOrigin[nbBits]), minWeight, sortedList + sortedRank, sortedListSize - sortedRank, nbBitsBaseline, symbol, wksp, wkspSize); } else { @@ -702,36 +706,22 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize void* dtPtr = (void*)(DTable + 1); HUF_DEltX2* dt = (HUF_DEltX2*)(dtPtr); uint* rankStart; - rankValCol_t* rankVal; - uint* rankStats; - uint* rankStart0; - sortedSymbol_t* sortedSymbol; - byte* weightList; - nuint spaceUsed32 = 0; - - rankVal = (rankValCol_t*)((uint*)(workSpace) + spaceUsed32); - spaceUsed32 += ((nuint)(sizeof(uint) * 13) * 12) >> 2; - rankStats = (uint*)(workSpace) + spaceUsed32; - spaceUsed32 += (nuint)(12 + 1); - rankStart0 = (uint*)(workSpace) + spaceUsed32; - spaceUsed32 += (nuint)(12 + 2); - sortedSymbol = (sortedSymbol_t*)(workSpace) + (spaceUsed32 * (nuint)(sizeof(uint))) / (nuint)(sizeof(sortedSymbol_t)); - spaceUsed32 += (nuint)((((((nuint)(sizeof(sortedSymbol_t)) * (uint)((255 + 1)))) + (((nuint)(sizeof(uint))) - 1)) & ~(((nuint)(sizeof(uint))) - 1)) >> 2); - weightList = (byte*)((uint*)(workSpace) + spaceUsed32); - spaceUsed32 += (nuint)((((uint)(((255 + 1))) + (((nuint)(sizeof(uint))) - 1)) & ~(((nuint)(sizeof(uint))) - 1)) >> 2); - if ((spaceUsed32 << 2) > wkspSize) + HUF_ReadDTableX2_Workspace* wksp = (HUF_ReadDTableX2_Workspace*)(workSpace); + + if ((nuint)(sizeof(HUF_ReadDTableX2_Workspace)) > wkspSize) { - return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_GENERIC))); } - rankStart = rankStart0 + 1; - memset((void*)(rankStats), (0), ((nuint)(sizeof(uint)) * (uint)((2 * 12 + 2 + 1)))); + rankStart = wksp->rankStart0 + 1; + memset((void*)(wksp->rankStats), (0), ((nuint)(sizeof(uint) * 13))); + memset((void*)(wksp->rankStart0), (0), ((nuint)(sizeof(uint) * 14))); if (maxTableLog > 12) { return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); } - iSize = HUF_readStats(weightList, (nuint)(255 + 1), rankStats, &nbSymbols, &tableLog, src, srcSize); + iSize = HUF_readStats_wksp((byte*)wksp->weightList, (nuint)(255 + 1), (uint*)wksp->rankStats, &nbSymbols, &tableLog, src, srcSize, (void*)wksp->calleeWksp, (nuint)(sizeof(uint) * 218), 0); if ((ERR_isError(iSize)) != 0) { return iSize; @@ -742,7 +732,7 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_tableLog_tooLarge))); } - for (maxW = tableLog; rankStats[maxW] == 0; maxW--) + for (maxW = tableLog; wksp->rankStats[maxW] == 0; maxW--) { } @@ -754,7 +744,7 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize { uint curr = nextRankStart; - nextRankStart += rankStats[w]; + nextRankStart += wksp->rankStats[w]; rankStart[w] = curr; } @@ -768,11 +758,11 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize for (s = 0; s < nbSymbols; s++) { - uint w = weightList[s]; + uint w = wksp->weightList[s]; uint r = rankStart[w]++; - sortedSymbol[r].symbol = (byte)(s); - sortedSymbol[r].weight = (byte)(w); + wksp->sortedSymbol[r].symbol = (byte)(s); + wksp->sortedSymbol[r].weight = (byte)(w); } rankStart[0] = 0; @@ -780,7 +770,7 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize { - uint* rankVal0 = (uint*)(rankVal[0]); + uint* rankVal0 = (uint*)(wksp->rankVal[0]); { @@ -792,7 +782,7 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize { uint curr = nextRankVal; - nextRankVal += rankStats[w] << (int)(w + (uint)rescale); + nextRankVal += wksp->rankStats[w] << (int)(w + (uint)rescale); rankVal0[w] = curr; } } @@ -804,7 +794,7 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize for (consumed = minBits; consumed < maxTableLog - minBits + 1; consumed++) { - uint* rankValPtr = (uint*)(rankVal[consumed]); + uint* rankValPtr = (uint*)(wksp->rankVal[consumed]); uint w; for (w = 1; w < maxW + 1; w++) @@ -815,7 +805,7 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize } } - HUF_fillDTableX2(dt, maxTableLog, sortedSymbol, sizeOfSort, rankStart0, rankVal, maxW, tableLog + 1); + HUF_fillDTableX2(dt, maxTableLog, (sortedSymbol_t*)wksp->sortedSymbol, sizeOfSort, (uint*)wksp->rankStart0, wksp->rankVal, maxW, tableLog + 1, (uint*)wksp->calleeWksp, (nuint)(sizeof(uint) * 218) / (nuint)(sizeof(uint))); dtd.tableLog = (byte)(maxTableLog); dtd.tableType = 1; memcpy((void*)(DTable), (void*)(&dtd), ((nuint)(sizeof(DTableDesc)))); @@ -823,7 +813,6 @@ public static nuint HUF_readDTableX2_wksp(uint* DTable, void* src, nuint srcSize } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static uint HUF_decodeSymbolX2(void* op, BIT_DStream_t* DStream, HUF_DEltX2* dt, uint dtLog) { nuint val = BIT_lookBitsFast(DStream, dtLog); @@ -1675,16 +1664,16 @@ public static nuint HUF_decompress4X_hufOnly_wksp_bmi2(uint* dctx, void* dst, nu public static nuint HUF_readDTableX1(uint* DTable, void* src, nuint srcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_readDTableX1_wksp(DTable, src, srcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_readDTableX1_wksp(DTable, src, srcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } public static nuint HUF_decompress1X1_DCtx(uint* DCtx, void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_decompress1X1_DCtx_wksp(DCtx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_decompress1X1_DCtx_wksp(DCtx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } public static nuint HUF_decompress1X1(void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) @@ -1698,16 +1687,16 @@ public static nuint HUF_decompress1X1(void* dst, nuint dstSize, void* cSrc, nuin public static nuint HUF_readDTableX2(uint* DTable, void* src, nuint srcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_readDTableX2_wksp(DTable, src, srcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_readDTableX2_wksp(DTable, src, srcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } public static nuint HUF_decompress1X2_DCtx(uint* DCtx, void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_decompress1X2_DCtx_wksp(DCtx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_decompress1X2_DCtx_wksp(DCtx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } public static nuint HUF_decompress1X2(void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) @@ -1721,9 +1710,9 @@ public static nuint HUF_decompress1X2(void* dst, nuint dstSize, void* cSrc, nuin public static nuint HUF_decompress4X1_DCtx(uint* dctx, void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } /* **************************************** @@ -1740,9 +1729,9 @@ public static nuint HUF_decompress4X1(void* dst, nuint dstSize, void* cSrc, nuin public static nuint HUF_decompress4X2_DCtx(uint* dctx, void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } public static nuint HUF_decompress4X2(void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) @@ -1832,16 +1821,16 @@ public static nuint HUF_decompress4X_DCtx(uint* dctx, void* dst, nuint dstSize, public static nuint HUF_decompress4X_hufOnly(uint* dctx, void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_decompress4X_hufOnly_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_decompress4X_hufOnly_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } public static nuint HUF_decompress1X_DCtx(uint* dctx, void* dst, nuint dstSize, void* cSrc, nuint cSrcSize) { - uint* workSpace = stackalloc uint[512]; + uint* workSpace = stackalloc uint[640]; - return HUF_decompress1X_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 512)); + return HUF_decompress1X_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, (void*)workSpace, (nuint)(sizeof(uint) * 640)); } } } diff --git a/src/ZstdSharp/Unsafe/Mem.cs b/src/ZstdSharp/Unsafe/Mem.cs index c4861a4..2ead3bb 100644 --- a/src/ZstdSharp/Unsafe/Mem.cs +++ b/src/ZstdSharp/Unsafe/Mem.cs @@ -11,7 +11,7 @@ namespace ZstdSharp.Unsafe { public static unsafe partial class Methods { - /*-************************************************************** + /*-************************************************************** * Memory I/O API *****************************************************************/ /*=== Static platform detection ===*/ diff --git a/src/ZstdSharp/Unsafe/Xxhash.cs b/src/ZstdSharp/Unsafe/Xxhash.cs index e69be23..2cdb1ef 100644 --- a/src/ZstdSharp/Unsafe/Xxhash.cs +++ b/src/ZstdSharp/Unsafe/Xxhash.cs @@ -10,19 +10,16 @@ namespace ZstdSharp.Unsafe { public static unsafe partial class Methods { - [InlineMethod.Inline] private static void XXH_memcpy(void* dest, void* src, ulong size) { memcpy((dest), (src), (size)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static uint XXH_readLE32(void* ptr) => BitConverter.IsLittleEndian ? *(uint*) ptr : BinaryPrimitives.ReverseEndianness(*(uint*) ptr); [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static ulong XXH_readLE64(void* ptr) => BitConverter.IsLittleEndian ? *(ulong*) ptr : BinaryPrimitives.ReverseEndianness(*(ulong*) ptr); @@ -41,7 +38,6 @@ public static uint XXH_versionNumber() return 0 * 100 * 100 + 6 * 100 + 2; } - [InlineMethod.Inline] private static ulong XXH64_round(ulong acc, ulong input) { acc += input * PRIME64_2; @@ -50,7 +46,6 @@ private static ulong XXH64_round(ulong acc, ulong input) return acc; } - [InlineMethod.Inline] private static ulong XXH64_mergeRound(ulong acc, ulong val) { val = XXH64_round(0, val); diff --git a/src/ZstdSharp/Unsafe/ZDICT_legacy_params_t.cs b/src/ZstdSharp/Unsafe/ZDICT_legacy_params_t.cs new file mode 100644 index 0000000..4845b4d --- /dev/null +++ b/src/ZstdSharp/Unsafe/ZDICT_legacy_params_t.cs @@ -0,0 +1,12 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public partial struct ZDICT_legacy_params_t + { + /* 0 means default; larger => select more => larger dictionary */ + public uint selectivityLevel; + + public ZDICT_params_t zParams; + } +} diff --git a/src/ZstdSharp/Unsafe/ZDICT_params_t.cs b/src/ZstdSharp/Unsafe/ZDICT_params_t.cs index 1326401..31cc6f1 100644 --- a/src/ZstdSharp/Unsafe/ZDICT_params_t.cs +++ b/src/ZstdSharp/Unsafe/ZDICT_params_t.cs @@ -10,7 +10,14 @@ public partial struct ZDICT_params_t /*< Write log to stderr; 0 = none (default); 1 = errors; 2 = progression; 3 = details; 4 = debug; */ public uint notificationLevel; - /*< force dictID value; 0 means auto mode (32-bits random value) */ + /*< force dictID value; 0 means auto mode (32-bits random value) + * NOTE: The zstd format reserves some dictionary IDs for future use. + * You may use them in private settings, but be warned that they + * may be used by zstd in a public dictionary registry in the future. + * These dictionary IDs are: + * - low range : <= 32767 + * - high range : >= (2^31) + */ public uint dictID; } } diff --git a/src/ZstdSharp/Unsafe/ZSTD_BuildCTableWksp.cs b/src/ZstdSharp/Unsafe/ZSTD_BuildCTableWksp.cs new file mode 100644 index 0000000..25bb00e --- /dev/null +++ b/src/ZstdSharp/Unsafe/ZSTD_BuildCTableWksp.cs @@ -0,0 +1,11 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public unsafe partial struct ZSTD_BuildCTableWksp + { + public fixed short norm[53]; + + public fixed uint wksp[182]; + } +} diff --git a/src/ZstdSharp/Unsafe/ZSTD_CCtx_params_s.cs b/src/ZstdSharp/Unsafe/ZSTD_CCtx_params_s.cs index 5b6ea1c..83059c2 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_CCtx_params_s.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_CCtx_params_s.cs @@ -55,6 +55,15 @@ public partial struct ZSTD_CCtx_params_s public int validateSequences; + /* Block splitting */ + public int splitBlocks; + + /* Param for deciding whether to use row-based matchfinder */ + public ZSTD_useRowMatchFinderMode_e useRowMatchFinder; + + /* Always load a dictionary in ext-dict mode (not prefix mode)? */ + public int deterministicRefPrefix; + /* Internal use, for createCCtxParams() and freeCCtxParams() only */ public ZSTD_customMem customMem; } diff --git a/src/ZstdSharp/Unsafe/ZSTD_CCtx_s.cs b/src/ZstdSharp/Unsafe/ZSTD_CCtx_s.cs index c41c865..0d8207f 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_CCtx_s.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_CCtx_s.cs @@ -16,6 +16,9 @@ public unsafe partial struct ZSTD_CCtx_s public ZSTD_CCtx_params_s appliedParams; + /* Param storage used by the simple API - not sticky. Must only be used in top-level simple API functions for storage. */ + public ZSTD_CCtx_params_s simpleApiParams; + public uint dictID; public nuint dictContentSize; diff --git a/src/ZstdSharp/Unsafe/ZSTD_CDict_s.cs b/src/ZstdSharp/Unsafe/ZSTD_CDict_s.cs index ea0fe39..d9cf92f 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_CDict_s.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_CDict_s.cs @@ -29,5 +29,11 @@ public unsafe partial struct ZSTD_CDict_s /* 0 indicates that advanced API was used to select CDict params */ public int compressionLevel; + + /* Indicates whether the CDict was created with params that would use + * row-based matchfinder. Unless the cdict is reloaded, we will use + * the same greedy/lazy matchfinder at compression time. + */ + public ZSTD_useRowMatchFinderMode_e useRowMatchFinder; } } diff --git a/src/ZstdSharp/Unsafe/ZSTD_DCtx_s.cs b/src/ZstdSharp/Unsafe/ZSTD_DCtx_s.cs index 7dd1d4f..f379c33 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_DCtx_s.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_DCtx_s.cs @@ -15,7 +15,7 @@ public unsafe partial struct ZSTD_DCtx_s public ZSTD_entropyDTables_t entropy; /* space needed when building huffman tables */ - public fixed uint workspace[512]; + public fixed uint workspace[640]; /* detect continuity */ public void* previousDstEnd; diff --git a/src/ZstdSharp/Unsafe/ZSTD_Vec256.cs b/src/ZstdSharp/Unsafe/ZSTD_Vec256.cs new file mode 100644 index 0000000..299ccf7 --- /dev/null +++ b/src/ZstdSharp/Unsafe/ZSTD_Vec256.cs @@ -0,0 +1,12 @@ +using System; +using System.Runtime.Intrinsics; + +namespace ZstdSharp.Unsafe +{ + public partial struct ZSTD_Vec256 + { + public Vector128 fst; + + public Vector128 snd; + } +} diff --git a/src/ZstdSharp/Unsafe/ZSTD_cParameter.cs b/src/ZstdSharp/Unsafe/ZSTD_cParameter.cs index e89f75c..ca42ead 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_cParameter.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_cParameter.cs @@ -35,5 +35,8 @@ public enum ZSTD_cParameter ZSTD_c_experimentalParam10 = 1007, ZSTD_c_experimentalParam11 = 1008, ZSTD_c_experimentalParam12 = 1009, + ZSTD_c_experimentalParam13 = 1010, + ZSTD_c_experimentalParam14 = 1011, + ZSTD_c_experimentalParam15 = 1012, } } diff --git a/src/ZstdSharp/Unsafe/ZSTD_cwksp.cs b/src/ZstdSharp/Unsafe/ZSTD_cwksp.cs index e25019b..78ff9e2 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_cwksp.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_cwksp.cs @@ -65,10 +65,11 @@ namespace ZstdSharp.Unsafe * - Tables: these are any of several different datastructures (hash tables, * chain tables, binary trees) that all respect a common format: they are * uint32_t arrays, all of whose values are between 0 and (nextSrc - base). - * Their sizes depend on the cparams. + * Their sizes depend on the cparams. These tables are 64-byte aligned. * * - Aligned: these buffers are used for various purposes that require 4 byte - * alignment, but don't require any initialization before they're used. + * alignment, but don't require any initialization before they're used. These + * buffers are each aligned to 64 bytes. * * - Buffers: these buffers are used for various purposes that don't require * any alignment or initialization before they're used. This means they can @@ -81,8 +82,7 @@ namespace ZstdSharp.Unsafe * * 1. Objects * 2. Buffers - * 3. Aligned - * 4. Tables + * 3. Aligned/Tables * * Attempts to reserve objects of different types out of order will fail. */ diff --git a/src/ZstdSharp/Unsafe/ZSTD_entropyDTables_t.cs b/src/ZstdSharp/Unsafe/ZSTD_entropyDTables_t.cs index 1a232ad..2a3e66b 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_entropyDTables_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_entropyDTables_t.cs @@ -1,6 +1,8 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { @@ -542,48 +544,45 @@ public ref ZSTD_seqSymbol this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 513); - public ref ZSTD_seqSymbol this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref ZSTD_seqSymbol this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator ZSTD_seqSymbol*(in _LLTable_e__FixedBuffer t) { - fixed (ZSTD_seqSymbol *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_LLTable_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static ZSTD_seqSymbol* operator +(in _LLTable_e__FixedBuffer t, uint index) { - fixed (ZSTD_seqSymbol *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_LLTable_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } @@ -851,48 +850,45 @@ public ref ZSTD_seqSymbol this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 257); - public ref ZSTD_seqSymbol this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref ZSTD_seqSymbol this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator ZSTD_seqSymbol*(in _OFTable_e__FixedBuffer t) { - fixed (ZSTD_seqSymbol *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_OFTable_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static ZSTD_seqSymbol* operator +(in _OFTable_e__FixedBuffer t, uint index) { - fixed (ZSTD_seqSymbol *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_OFTable_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } @@ -1416,48 +1412,45 @@ public ref ZSTD_seqSymbol this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 513); - public ref ZSTD_seqSymbol this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref ZSTD_seqSymbol this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator ZSTD_seqSymbol*(in _MLTable_e__FixedBuffer t) { - fixed (ZSTD_seqSymbol *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_MLTable_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static ZSTD_seqSymbol* operator +(in _MLTable_e__FixedBuffer t, uint index) { - fixed (ZSTD_seqSymbol *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_MLTable_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/ZSTD_fseCTablesMetadata_t.cs b/src/ZstdSharp/Unsafe/ZSTD_fseCTablesMetadata_t.cs index e723f23..4b46cfc 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_fseCTablesMetadata_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_fseCTablesMetadata_t.cs @@ -6,7 +6,7 @@ namespace ZstdSharp.Unsafe * Stores symbol compression modes for a super-block in {ll, ol, ml}Type, and * fse tables in fseTablesBuffer. * fseTablesSize refers to the size of fse tables in bytes. - * This metadata is populated in ZSTD_buildSuperBlockEntropy_sequences() */ + * This metadata is populated in ZSTD_buildBlockEntropyStats_sequences() */ public unsafe partial struct ZSTD_fseCTablesMetadata_t { public symbolEncodingType_e llType; @@ -19,7 +19,7 @@ public unsafe partial struct ZSTD_fseCTablesMetadata_t public nuint fseTablesSize; - /* This is to account for bug in 1.3.4. More detail in ZSTD_compressSubBlock_sequences() */ + /* This is to account for bug in 1.3.4. More detail in ZSTD_entropyCompressSeqStore_internal() */ public nuint lastCountSize; } } diff --git a/src/ZstdSharp/Unsafe/ZSTD_hufCTablesMetadata_t.cs b/src/ZstdSharp/Unsafe/ZSTD_hufCTablesMetadata_t.cs index 3e79a65..bfec959 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_hufCTablesMetadata_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_hufCTablesMetadata_t.cs @@ -2,14 +2,14 @@ namespace ZstdSharp.Unsafe { - /*-************************************* - * Superblock entropy buffer structs - ***************************************/ + /*********************************************** + * Entropy buffer statistics structs and funcs * + ***********************************************/ /** ZSTD_hufCTablesMetadata_t : * Stores Literals Block Type for a super-block in hType, and * huffman tree description in hufDesBuffer. * hufDesSize refers to the size of huffman tree description in bytes. - * This metadata is populated in ZSTD_buildSuperBlockEntropy_literal() */ + * This metadata is populated in ZSTD_buildBlockEntropyStats_literals() */ public unsafe partial struct ZSTD_hufCTablesMetadata_t { public symbolEncodingType_e hType; diff --git a/src/ZstdSharp/Unsafe/ZSTD_hufCTables_t.cs b/src/ZstdSharp/Unsafe/ZSTD_hufCTables_t.cs index 145b560..506c3e5 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_hufCTables_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_hufCTables_t.cs @@ -1,6 +1,8 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { @@ -273,48 +275,45 @@ public ref HUF_CElt_s this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 256); - public ref HUF_CElt_s this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref HUF_CElt_s this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator HUF_CElt_s*(in _CTable_e__FixedBuffer t) { - fixed (HUF_CElt_s *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_CTable_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static HUF_CElt_s* operator +(in _CTable_e__FixedBuffer t, uint index) { - fixed (HUF_CElt_s *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_CTable_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/ZSTD_longLengthType_e.cs b/src/ZstdSharp/Unsafe/ZSTD_longLengthType_e.cs new file mode 100644 index 0000000..ce0938f --- /dev/null +++ b/src/ZstdSharp/Unsafe/ZSTD_longLengthType_e.cs @@ -0,0 +1,11 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public enum ZSTD_longLengthType_e + { + ZSTD_llt_none = 0, + ZSTD_llt_literalLength = 1, + ZSTD_llt_matchLength = 2, + } +} diff --git a/src/ZstdSharp/Unsafe/ZSTD_matchState_t.cs b/src/ZstdSharp/Unsafe/ZSTD_matchState_t.cs index 2d38d47..dc0e657 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_matchState_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_matchState_t.cs @@ -22,12 +22,24 @@ public unsafe partial struct ZSTD_matchState_t /* dispatch table for matches of len==3 : larger == faster, more memory */ public uint hashLog3; + /* For row-based matchfinder: Hashlog based on nb of rows in the hashTable.*/ + public uint rowHashLog; + + /* For row-based matchFinder: A row-based table containing the hashes and head index. */ + public ushort* tagTable; + + /* For row-based matchFinder: a cache of hashes to improve speed */ + public fixed uint hashCache[8]; + public uint* hashTable; public uint* hashTable3; public uint* chainTable; + /* Non-zero if we should force non-contiguous load for the next window update. */ + public uint forceNonContiguous; + /* Indicates whether this matchState is using the * dedicated dictionary search structure. */ diff --git a/src/ZstdSharp/Unsafe/ZSTD_match_t.cs b/src/ZstdSharp/Unsafe/ZSTD_match_t.cs index aed3d5d..7774e18 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_match_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_match_t.cs @@ -2,6 +2,9 @@ namespace ZstdSharp.Unsafe { + /********************************* + * Compression internals structs * + *********************************/ public partial struct ZSTD_match_t { /* Offset code (offset + ZSTD_REP_MOVE) for the match */ diff --git a/src/ZstdSharp/Unsafe/ZSTD_outBufferMode_e.cs b/src/ZstdSharp/Unsafe/ZSTD_outBufferMode_e.cs deleted file mode 100644 index fb58e43..0000000 --- a/src/ZstdSharp/Unsafe/ZSTD_outBufferMode_e.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace ZstdSharp.Unsafe -{ - public enum ZSTD_outBufferMode_e - { - ZSTD_obm_buffered = 0, - ZSTD_obm_stable = 1, - } -} diff --git a/src/ZstdSharp/Unsafe/ZSTD_prefetch_e.cs b/src/ZstdSharp/Unsafe/ZSTD_prefetch_e.cs deleted file mode 100644 index db3471e..0000000 --- a/src/ZstdSharp/Unsafe/ZSTD_prefetch_e.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace ZstdSharp.Unsafe -{ - public enum ZSTD_prefetch_e - { - ZSTD_p_noPrefetch = 0, - ZSTD_p_prefetch = 1, - } -} diff --git a/src/ZstdSharp/Unsafe/ZSTD_symbolEncodingTypeStats_t.cs b/src/ZstdSharp/Unsafe/ZSTD_symbolEncodingTypeStats_t.cs new file mode 100644 index 0000000..7f2295d --- /dev/null +++ b/src/ZstdSharp/Unsafe/ZSTD_symbolEncodingTypeStats_t.cs @@ -0,0 +1,21 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + /* Type returned by ZSTD_buildSequencesStatistics containing finalized symbol encoding types + * and size of the sequences statistics + */ + public partial struct ZSTD_symbolEncodingTypeStats_t + { + public uint LLtype; + + public uint Offtype; + + public uint MLtype; + + public nuint size; + + /* Accounts for bug in 1.3.4. More detail in ZSTD_entropyCompressSeqStore_internal() */ + public nuint lastCountSize; + } +} diff --git a/src/ZstdSharp/Unsafe/ZSTD_useRowMatchFinderMode_e.cs b/src/ZstdSharp/Unsafe/ZSTD_useRowMatchFinderMode_e.cs new file mode 100644 index 0000000..45395fb --- /dev/null +++ b/src/ZstdSharp/Unsafe/ZSTD_useRowMatchFinderMode_e.cs @@ -0,0 +1,11 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public enum ZSTD_useRowMatchFinderMode_e + { + ZSTD_urm_auto = 0, + ZSTD_urm_disableRowMatchFinder = 1, + ZSTD_urm_enableRowMatchFinder = 2, + } +} diff --git a/src/ZstdSharp/Unsafe/ZSTD_window_t.cs b/src/ZstdSharp/Unsafe/ZSTD_window_t.cs index 6fdb1c2..67c24d7 100644 --- a/src/ZstdSharp/Unsafe/ZSTD_window_t.cs +++ b/src/ZstdSharp/Unsafe/ZSTD_window_t.cs @@ -18,5 +18,11 @@ public unsafe partial struct ZSTD_window_t /* below that point, no more valid data */ public uint lowLimit; + + /* Number of times overflow correction has run since + * ZSTD_window_init(). Useful for debugging coredumps + * and for ZSTD_WINDOW_OVERFLOW_CORRECT_FREQUENTLY. + */ + public uint nbOverflowCorrections; } } diff --git a/src/ZstdSharp/Unsafe/Zdict.cs b/src/ZstdSharp/Unsafe/Zdict.cs index df6ea2e..1c98409 100644 --- a/src/ZstdSharp/Unsafe/Zdict.cs +++ b/src/ZstdSharp/Unsafe/Zdict.cs @@ -1,5 +1,4 @@ using System; -using System.Numerics; using static ZstdSharp.UnsafeHelper; namespace ZstdSharp.Unsafe @@ -19,53 +18,6 @@ public static uint ZDICT_isError(nuint errorCode) return ERR_getErrorName(errorCode); } - /*====== Helper functions ======*/ - public static uint ZDICT_getDictID(void* dictBuffer, nuint dictSize) - { - if (dictSize < 8) - { - return 0; - } - - if (MEM_readLE32(dictBuffer) != 0xEC30A437) - { - return 0; - } - - return MEM_readLE32((void*)((sbyte*)(dictBuffer) + 4)); - } - - public static nuint ZDICT_getDictHeaderSize(void* dictBuffer, nuint dictSize) - { - nuint headerSize; - - if (dictSize <= 8 || MEM_readLE32(dictBuffer) != 0xEC30A437) - { - return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_dictionary_corrupted))); - } - - - { - ZSTD_compressedBlockState_t* bs = (ZSTD_compressedBlockState_t*)(malloc((nuint)(4592))); - uint* wksp = (uint*)(malloc((nuint)(((6 << 10) + 256)))); - - if (bs == null || wksp == null) - { - headerSize = (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_memory_allocation))); - } - else - { - ZSTD_reset_compressedBlockState(bs); - headerSize = ZSTD_loadCEntropy(bs, (void*)wksp, dictBuffer, dictSize); - } - - free((void*)bs); - free((void*)wksp); - } - - return headerSize; - } - private static void ZDICT_countEStats(EStats_ress_t esr, ZSTD_parameters* @params, uint* countLit, uint* offsetcodeCount, uint* matchlengthCount, uint* litlengthCount, uint* repOffsets, void* src, nuint srcSize, uint notificationLevel) { nuint blockSizeMax = (nuint)((((1 << 17)) < (1 << (int)@params->cParams.windowLog) ? ((1 << 17)) : (1 << (int)@params->cParams.windowLog))); diff --git a/src/ZstdSharp/Unsafe/ZstdCommon.cs b/src/ZstdSharp/Unsafe/ZstdCommon.cs index 2bfe3cc..c1a7985 100644 --- a/src/ZstdSharp/Unsafe/ZstdCommon.cs +++ b/src/ZstdSharp/Unsafe/ZstdCommon.cs @@ -10,14 +10,14 @@ public static unsafe partial class Methods ******************************************/ public static uint ZSTD_versionNumber() { - return (uint)((1 * 100 * 100 + 4 * 100 + 9)); + return (uint)((1 * 100 * 100 + 5 * 100 + 0)); } /*! ZSTD_versionString() : * Return runtime library version, like "1.4.5". Requires v1.3.0+. */ public static sbyte* ZSTD_versionString() { - return GetStringPointer("1.4.9"); + return GetStringPointer("1.5.0"); } /*! ZSTD_isError() : diff --git a/src/ZstdSharp/Unsafe/ZstdCompress.cs b/src/ZstdSharp/Unsafe/ZstdCompress.cs index 0ffef4b..97409cf 100644 --- a/src/ZstdSharp/Unsafe/ZstdCompress.cs +++ b/src/ZstdSharp/Unsafe/ZstdCompress.cs @@ -178,7 +178,7 @@ private static nuint ZSTD_sizeof_mtctx(ZSTD_CCtx_s* cctx) return 0; } - /*! ZSTD_sizeof_*() : + /*! ZSTD_sizeof_*() : Requires v1.4.0+ * These functions give the _current_ memory usage of selected object. * Note that object memory usage can evolve (increase or decrease) over time. */ public static nuint ZSTD_sizeof_CCtx(ZSTD_CCtx_s* cctx) @@ -202,6 +202,62 @@ public static nuint ZSTD_sizeof_CStream(ZSTD_CCtx_s* zcs) return &(ctx->seqStore); } + /* Returns true if the strategy supports using a row based matchfinder */ + private static int ZSTD_rowMatchFinderSupported(ZSTD_strategy strategy) + { + return ((strategy >= ZSTD_strategy.ZSTD_greedy && strategy <= ZSTD_strategy.ZSTD_lazy2) ? 1 : 0); + } + + /* Returns true if the strategy and useRowMatchFinder mode indicate that we will use the row based matchfinder + * for this compression. + */ + private static int ZSTD_rowMatchFinderUsed(ZSTD_strategy strategy, ZSTD_useRowMatchFinderMode_e mode) + { + assert(mode != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + return (((ZSTD_rowMatchFinderSupported(strategy)) != 0 && (mode == ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder)) ? 1 : 0); + } + + /* Returns row matchfinder usage enum given an initial mode and cParams */ + private static ZSTD_useRowMatchFinderMode_e ZSTD_resolveRowMatchFinderMode(ZSTD_useRowMatchFinderMode_e mode, ZSTD_compressionParameters* cParams) + { + int kHasSIMD128 = 1; + + if (mode != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto) + { + return mode; + } + + mode = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_disableRowMatchFinder; + if ((ZSTD_rowMatchFinderSupported(cParams->strategy)) == 0) + { + return mode; + } + + if (kHasSIMD128 != 0) + { + if (cParams->windowLog > 14) + { + mode = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder; + } + } + else + { + if (cParams->windowLog > 17) + { + mode = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder; + } + } + + return mode; + } + + /* Returns 1 if the arguments indicate that we should allocate a chainTable, 0 otherwise */ + private static int ZSTD_allocateChainTable(ZSTD_strategy strategy, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, uint forDDSDict) + { + assert(useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + return ((forDDSDict != 0 || ((strategy != ZSTD_strategy.ZSTD_fast) && (ZSTD_rowMatchFinderUsed(strategy, useRowMatchFinder)) == 0)) ? 1 : 0); + } + /* Returns 1 if compression parameters are such that we should * enable long distance matching (wlog >= 27, strategy >= btopt). * Returns 0 otherwise. @@ -211,6 +267,15 @@ private static uint ZSTD_CParams_shouldEnableLdm(ZSTD_compressionParameters* cPa return ((cParams->strategy >= ZSTD_strategy.ZSTD_btopt && cParams->windowLog >= 27) ? 1U : 0U); } + /* Returns 1 if compression parameters are such that we should + * enable blockSplitter (wlog >= 17, strategy >= btopt). + * Returns 0 otherwise. + */ + private static uint ZSTD_CParams_useBlockSplitter(ZSTD_compressionParameters* cParams) + { + return ((cParams->strategy >= ZSTD_strategy.ZSTD_btopt && cParams->windowLog >= 17) ? 1U : 0U); + } + private static ZSTD_CCtx_params_s ZSTD_makeCCtxParamsFromCParams(ZSTD_compressionParameters cParams) { ZSTD_CCtx_params_s cctxParams; @@ -225,6 +290,12 @@ private static ZSTD_CCtx_params_s ZSTD_makeCCtxParamsFromCParams(ZSTD_compressio assert(cctxParams.ldmParams.hashRateLog < 32); } + if ((ZSTD_CParams_useBlockSplitter(&cParams)) != 0) + { + cctxParams.splitBlocks = 1; + } + + cctxParams.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams.useRowMatchFinder, &cParams); assert((ZSTD_checkCParams(cParams)) == 0); return cctxParams; } @@ -261,7 +332,7 @@ private static ZSTD_CCtx_params_s ZSTD_makeCCtxParamsFromCParams(ZSTD_compressio * These parameters will be applied to * all subsequent frames. * - ZSTD_compressStream2() : Do compression using the CCtx. - * - ZSTD_freeCCtxParams() : Free the memory. + * - ZSTD_freeCCtxParams() : Free the memory, accept NULL pointer. * * This can be used with ZSTD_estimateCCtxSize_advanced_usingCCtxParams() * for static allocation of CCtx for single-threaded compression. @@ -318,6 +389,7 @@ private static void ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params_s* cctxParams cctxParams->cParams = @params->cParams; cctxParams->fParams = @params->fParams; cctxParams->compressionLevel = compressionLevel; + cctxParams->useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams->useRowMatchFinder, &@params->cParams); } /*! ZSTD_CCtxParams_init_advanced() : @@ -587,6 +659,27 @@ public static ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter param) bounds.lowerBound = 0; } + bounds.upperBound = 1; + return bounds; + case ZSTD_cParameter.ZSTD_c_experimentalParam13: + { + bounds.lowerBound = 0; + } + + bounds.upperBound = 1; + return bounds; + case ZSTD_cParameter.ZSTD_c_experimentalParam14: + { + bounds.lowerBound = (int)(ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + } + + bounds.upperBound = (int)(ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder); + return bounds; + case ZSTD_cParameter.ZSTD_c_experimentalParam15: + { + bounds.lowerBound = 0; + } + bounds.upperBound = 1; return bounds; default: @@ -662,6 +755,9 @@ private static int ZSTD_isUpdateAuthorized(ZSTD_cParameter param) case ZSTD_cParameter.ZSTD_c_experimentalParam10: case ZSTD_cParameter.ZSTD_c_experimentalParam11: case ZSTD_cParameter.ZSTD_c_experimentalParam12: + case ZSTD_cParameter.ZSTD_c_experimentalParam13: + case ZSTD_cParameter.ZSTD_c_experimentalParam14: + case ZSTD_cParameter.ZSTD_c_experimentalParam15: default: { return 0; @@ -739,6 +835,9 @@ public static nuint ZSTD_CCtx_setParameter(ZSTD_CCtx_s* cctx, ZSTD_cParameter pa case ZSTD_cParameter.ZSTD_c_experimentalParam10: case ZSTD_cParameter.ZSTD_c_experimentalParam11: case ZSTD_cParameter.ZSTD_c_experimentalParam12: + case ZSTD_cParameter.ZSTD_c_experimentalParam13: + case ZSTD_cParameter.ZSTD_c_experimentalParam14: + case ZSTD_cParameter.ZSTD_c_experimentalParam15: { break; } @@ -756,7 +855,7 @@ public static nuint ZSTD_CCtx_setParameter(ZSTD_CCtx_s* cctx, ZSTD_cParameter pa return ZSTD_CCtxParams_setParameter(&cctx->requestedParams, param, value); } - /*! ZSTD_CCtxParams_setParameter() : + /*! ZSTD_CCtxParams_setParameter() : Requires v1.4.0+ * Similar to ZSTD_CCtx_setParameter. * Set one compression parameter, selected by enum ZSTD_cParameter. * Parameters must be applied to a ZSTD_CCtx using @@ -1129,6 +1228,39 @@ public static nuint ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params_s* CCtxParams, CCtxParams->validateSequences = value; return (nuint)CCtxParams->validateSequences; + case ZSTD_cParameter.ZSTD_c_experimentalParam13: + { + if ((ZSTD_cParam_withinBounds(ZSTD_cParameter.ZSTD_c_experimentalParam13, value)) == 0) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_parameter_outOfBound))); + } + + } + + CCtxParams->splitBlocks = value; + return (nuint)CCtxParams->splitBlocks; + case ZSTD_cParameter.ZSTD_c_experimentalParam14: + { + if ((ZSTD_cParam_withinBounds(ZSTD_cParameter.ZSTD_c_experimentalParam14, value)) == 0) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_parameter_outOfBound))); + } + + } + + CCtxParams->useRowMatchFinder = (ZSTD_useRowMatchFinderMode_e)(value); + return (nuint)CCtxParams->useRowMatchFinder; + case ZSTD_cParameter.ZSTD_c_experimentalParam15: + { + if ((ZSTD_cParam_withinBounds(ZSTD_cParameter.ZSTD_c_experimentalParam15, value)) == 0) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_parameter_outOfBound))); + } + + } + + CCtxParams->deterministicRefPrefix = (!(value == 0) ? 1 : 0); + return (nuint)CCtxParams->deterministicRefPrefix; default: { @@ -1351,6 +1483,24 @@ public static nuint ZSTD_CCtxParams_getParameter(ZSTD_CCtx_params_s* CCtxParams, *value = (int)(CCtxParams->validateSequences); } + break; + case ZSTD_cParameter.ZSTD_c_experimentalParam13: + { + *value = (int)(CCtxParams->splitBlocks); + } + + break; + case ZSTD_cParameter.ZSTD_c_experimentalParam14: + { + *value = (int)(CCtxParams->useRowMatchFinder); + } + + break; + case ZSTD_cParameter.ZSTD_c_experimentalParam15: + { + *value = (int)(CCtxParams->deterministicRefPrefix); + } + break; default: { @@ -1504,7 +1654,7 @@ public static nuint ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx_s* cctx, void return ZSTD_CCtx_loadDictionary_advanced(cctx, dict, dictSize, ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef, ZSTD_dictContentType_e.ZSTD_dct_auto); } - /*! ZSTD_CCtx_loadDictionary() : + /*! ZSTD_CCtx_loadDictionary() : Requires v1.4.0+ * Create an internal CDict from `dict` buffer. * Decompression will have to use same dictionary. * @result : 0, or an error code (which can be tested with ZSTD_isError()). @@ -1526,7 +1676,7 @@ public static nuint ZSTD_CCtx_loadDictionary(ZSTD_CCtx_s* cctx, void* dict, nuin return ZSTD_CCtx_loadDictionary_advanced(cctx, dict, dictSize, ZSTD_dictLoadMethod_e.ZSTD_dlm_byCopy, ZSTD_dictContentType_e.ZSTD_dct_auto); } - /*! ZSTD_CCtx_refCDict() : + /*! ZSTD_CCtx_refCDict() : Requires v1.4.0+ * Reference a prepared dictionary, to be used for all next compressed frames. * Note that compression parameters are enforced from within CDict, * and supersede any compression parameter previously set within CCtx. @@ -1561,7 +1711,7 @@ public static nuint ZSTD_CCtx_refThreadPool(ZSTD_CCtx_s* cctx, void* pool) return 0; } - /*! ZSTD_CCtx_refPrefix() : + /*! ZSTD_CCtx_refPrefix() : Requires v1.4.0+ * Reference a prefix (single-usage dictionary) for next compressed frame. * A prefix is **only used once**. Tables are discarded at end of frame (ZSTD_e_end). * Decompression will need same prefix to properly regenerate data. @@ -2014,32 +2164,35 @@ public static ZSTD_compressionParameters ZSTD_getCParamsFromCCtxParams(ZSTD_CCtx return ZSTD_adjustCParams_internal(cParams, srcSizeHint, dictSize, mode); } - private static nuint ZSTD_sizeof_matchState(ZSTD_compressionParameters* cParams, uint forCCtx) + private static nuint ZSTD_sizeof_matchState(ZSTD_compressionParameters* cParams, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, uint enableDedicatedDictSearch, uint forCCtx) { - nuint chainSize = (cParams->strategy == ZSTD_strategy.ZSTD_fast) ? 0 : ((nuint)(1) << (int)cParams->chainLog); + nuint chainSize = (ZSTD_allocateChainTable(cParams->strategy, useRowMatchFinder, ((enableDedicatedDictSearch != 0 && forCCtx == 0) ? 1U : 0U))) != 0 ? ((nuint)(1) << (int)cParams->chainLog) : 0; nuint hSize = ((nuint)(1)) << (int)cParams->hashLog; uint hashLog3 = (uint)((forCCtx != 0 && cParams->minMatch == 3) ? ((17) < (cParams->windowLog) ? (17) : (cParams->windowLog)) : 0); nuint h3Size = hashLog3 != 0 ? ((nuint)(1)) << (int)hashLog3 : 0; nuint tableSpace = chainSize * (nuint)(4) + hSize * (nuint)(4) + h3Size * (nuint)(4); - nuint optPotentialSpace = ZSTD_cwksp_alloc_size((uint)((52 + 1)) * (nuint)(4)) + ZSTD_cwksp_alloc_size((uint)((35 + 1)) * (nuint)(4)) + ZSTD_cwksp_alloc_size((uint)((31 + 1)) * (nuint)(4)) + ZSTD_cwksp_alloc_size((uint)((1 << 8)) * (nuint)(4)) + ZSTD_cwksp_alloc_size((uint)(((1 << 12) + 1)) * (nuint)(8)) + ZSTD_cwksp_alloc_size((uint)(((1 << 12) + 1)) * (nuint)(28)); + nuint optPotentialSpace = ZSTD_cwksp_aligned_alloc_size((uint)((52 + 1)) * (nuint)(4)) + ZSTD_cwksp_aligned_alloc_size((uint)((35 + 1)) * (nuint)(4)) + ZSTD_cwksp_aligned_alloc_size((uint)((31 + 1)) * (nuint)(4)) + ZSTD_cwksp_aligned_alloc_size((uint)((1 << 8)) * (nuint)(4)) + ZSTD_cwksp_aligned_alloc_size((uint)(((1 << 12) + 1)) * (nuint)(8)) + ZSTD_cwksp_aligned_alloc_size((uint)(((1 << 12) + 1)) * (nuint)(28)); + nuint lazyAdditionalSpace = (ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) != 0 ? ZSTD_cwksp_aligned_alloc_size(hSize * (nuint)(2)) : 0; nuint optSpace = (forCCtx != 0 && (cParams->strategy >= ZSTD_strategy.ZSTD_btopt)) ? optPotentialSpace : 0; + nuint slackSpace = ZSTD_cwksp_slack_space_required(); - return tableSpace + optSpace; + assert(useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + return tableSpace + optSpace + slackSpace + lazyAdditionalSpace; } - private static nuint ZSTD_estimateCCtxSize_usingCCtxParams_internal(ZSTD_compressionParameters* cParams, ldmParams_t* ldmParams, int isStatic, nuint buffInSize, nuint buffOutSize, ulong pledgedSrcSize) + private static nuint ZSTD_estimateCCtxSize_usingCCtxParams_internal(ZSTD_compressionParameters* cParams, ldmParams_t* ldmParams, int isStatic, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, nuint buffInSize, nuint buffOutSize, ulong pledgedSrcSize) { nuint windowSize = ((1) > ((nuint)((((ulong)(1) << (int)cParams->windowLog)) < (pledgedSrcSize) ? (((ulong)(1) << (int)cParams->windowLog)) : (pledgedSrcSize))) ? (1) : ((nuint)((((ulong)(1) << (int)cParams->windowLog)) < (pledgedSrcSize) ? (((ulong)(1) << (int)cParams->windowLog)) : (pledgedSrcSize)))); nuint blockSize = (nuint)((uint)(((1 << 17))) < (windowSize) ? ((1 << 17)) : (windowSize)); uint divider = (uint)((cParams->minMatch == 3) ? 3 : 4); nuint maxNbSeq = blockSize / divider; - nuint tokenSpace = ZSTD_cwksp_alloc_size(32 + blockSize) + ZSTD_cwksp_alloc_size(maxNbSeq * (nuint)(8)) + 3 * ZSTD_cwksp_alloc_size(maxNbSeq * (nuint)(1)); + nuint tokenSpace = ZSTD_cwksp_alloc_size(32 + blockSize) + ZSTD_cwksp_aligned_alloc_size(maxNbSeq * (nuint)(8)) + 3 * ZSTD_cwksp_alloc_size(maxNbSeq * (nuint)(1)); nuint entropySpace = ZSTD_cwksp_alloc_size(((uint)(((6 << 10) + 256)) + ((nuint)(4) * (uint)((((35) > (52) ? (35) : (52)) + 2))))); nuint blockStateSpace = 2 * ZSTD_cwksp_alloc_size((nuint)(4592)); - nuint matchStateSize = ZSTD_sizeof_matchState(cParams, 1); + nuint matchStateSize = ZSTD_sizeof_matchState(cParams, useRowMatchFinder, 0, 1); nuint ldmSpace = ZSTD_ldm_getTableSize(*ldmParams); nuint maxNbLdmSeq = ZSTD_ldm_getMaxNbSeq(*ldmParams, blockSize); - nuint ldmSeqSpace = ldmParams->enableLdm != 0 ? ZSTD_cwksp_alloc_size(maxNbLdmSeq * (nuint)(12)) : 0; + nuint ldmSeqSpace = ldmParams->enableLdm != 0 ? ZSTD_cwksp_aligned_alloc_size(maxNbLdmSeq * (nuint)(12)) : 0; nuint bufferSpace = ZSTD_cwksp_alloc_size(buffInSize) + ZSTD_cwksp_alloc_size(buffOutSize); nuint cctxSpace = isStatic != 0 ? ZSTD_cwksp_alloc_size((nuint)(sizeof(ZSTD_CCtx_s))) : 0; nuint neededSpace = cctxSpace + entropySpace + blockStateSpace + ldmSpace + ldmSeqSpace + matchStateSize + tokenSpace + bufferSpace; @@ -2050,27 +2203,51 @@ private static nuint ZSTD_estimateCCtxSize_usingCCtxParams_internal(ZSTD_compres public static nuint ZSTD_estimateCCtxSize_usingCCtxParams(ZSTD_CCtx_params_s* @params) { ZSTD_compressionParameters cParams = ZSTD_getCParamsFromCCtxParams(@params, (unchecked(0UL - 1)), 0, ZSTD_cParamMode_e.ZSTD_cpm_noAttachDict); + ZSTD_useRowMatchFinderMode_e useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(@params->useRowMatchFinder, &cParams); if (@params->nbWorkers > 0) { return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_GENERIC))); } - return ZSTD_estimateCCtxSize_usingCCtxParams_internal(&cParams, &@params->ldmParams, 1, 0, 0, (unchecked(0UL - 1))); + return ZSTD_estimateCCtxSize_usingCCtxParams_internal(&cParams, &@params->ldmParams, 1, useRowMatchFinder, 0, 0, (unchecked(0UL - 1))); } public static nuint ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams) { - ZSTD_CCtx_params_s @params = ZSTD_makeCCtxParamsFromCParams(cParams); + ZSTD_CCtx_params_s initialParams = ZSTD_makeCCtxParamsFromCParams(cParams); - return ZSTD_estimateCCtxSize_usingCCtxParams(&@params); + if ((ZSTD_rowMatchFinderSupported(cParams.strategy)) != 0) + { + nuint noRowCCtxSize; + nuint rowCCtxSize; + + initialParams.useRowMatchFinder = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_disableRowMatchFinder; + noRowCCtxSize = ZSTD_estimateCCtxSize_usingCCtxParams(&initialParams); + initialParams.useRowMatchFinder = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder; + rowCCtxSize = ZSTD_estimateCCtxSize_usingCCtxParams(&initialParams); + return ((noRowCCtxSize) > (rowCCtxSize) ? (noRowCCtxSize) : (rowCCtxSize)); + } + else + { + return ZSTD_estimateCCtxSize_usingCCtxParams(&initialParams); + } } private static nuint ZSTD_estimateCCtxSize_internal(int compressionLevel) { - ZSTD_compressionParameters cParams = ZSTD_getCParams_internal(compressionLevel, (unchecked(0UL - 1)), 0, ZSTD_cParamMode_e.ZSTD_cpm_noAttachDict); + int tier = 0; + nuint largestSize = 0; + - return ZSTD_estimateCCtxSize_usingCParams(cParams); + for (; tier < 4; ++tier) + { + ZSTD_compressionParameters cParams = ZSTD_getCParams_internal(compressionLevel, srcSizeTiers[tier], 0, ZSTD_cParamMode_e.ZSTD_cpm_noAttachDict); + + largestSize = ((ZSTD_estimateCCtxSize_usingCParams(cParams)) > (largestSize) ? (ZSTD_estimateCCtxSize_usingCParams(cParams)) : (largestSize)); + } + + return largestSize; } /*! ZSTD_estimate*() : @@ -2127,16 +2304,31 @@ public static nuint ZSTD_estimateCStreamSize_usingCCtxParams(ZSTD_CCtx_params_s* nuint blockSize = (nuint)((uint)(((1 << 17))) < ((nuint)(1) << (int)cParams.windowLog) ? ((1 << 17)) : ((nuint)(1) << (int)cParams.windowLog)); nuint inBuffSize = (@params->inBufferMode == ZSTD_bufferMode_e.ZSTD_bm_buffered) ? ((nuint)(1) << (int)cParams.windowLog) + blockSize : 0; nuint outBuffSize = (@params->outBufferMode == ZSTD_bufferMode_e.ZSTD_bm_buffered) ? ZSTD_compressBound(blockSize) + 1 : 0; + ZSTD_useRowMatchFinderMode_e useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(@params->useRowMatchFinder, &@params->cParams); - return ZSTD_estimateCCtxSize_usingCCtxParams_internal(&cParams, &@params->ldmParams, 1, inBuffSize, outBuffSize, (unchecked(0UL - 1))); + return ZSTD_estimateCCtxSize_usingCCtxParams_internal(&cParams, &@params->ldmParams, 1, useRowMatchFinder, inBuffSize, outBuffSize, (unchecked(0UL - 1))); } } public static nuint ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams) { - ZSTD_CCtx_params_s @params = ZSTD_makeCCtxParamsFromCParams(cParams); + ZSTD_CCtx_params_s initialParams = ZSTD_makeCCtxParamsFromCParams(cParams); - return ZSTD_estimateCStreamSize_usingCCtxParams(&@params); + if ((ZSTD_rowMatchFinderSupported(cParams.strategy)) != 0) + { + nuint noRowCCtxSize; + nuint rowCCtxSize; + + initialParams.useRowMatchFinder = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_disableRowMatchFinder; + noRowCCtxSize = ZSTD_estimateCStreamSize_usingCCtxParams(&initialParams); + initialParams.useRowMatchFinder = ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder; + rowCCtxSize = ZSTD_estimateCStreamSize_usingCCtxParams(&initialParams); + return ((noRowCCtxSize) > (rowCCtxSize) ? (noRowCCtxSize) : (rowCCtxSize)); + } + else + { + return ZSTD_estimateCStreamSize_usingCCtxParams(&initialParams); + } } private static nuint ZSTD_estimateCStreamSize_internal(int compressionLevel) @@ -2251,13 +2443,14 @@ private static void ZSTD_invalidateMatchState(ZSTD_matchState_t* ms) ms->dictMatchState = null; } - private static nuint ZSTD_reset_matchState(ZSTD_matchState_t* ms, ZSTD_cwksp* ws, ZSTD_compressionParameters* cParams, ZSTD_compResetPolicy_e crp, ZSTD_indexResetPolicy_e forceResetIndex, ZSTD_resetTarget_e forWho) + private static nuint ZSTD_reset_matchState(ZSTD_matchState_t* ms, ZSTD_cwksp* ws, ZSTD_compressionParameters* cParams, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, ZSTD_compResetPolicy_e crp, ZSTD_indexResetPolicy_e forceResetIndex, ZSTD_resetTarget_e forWho) { - nuint chainSize = (cParams->strategy == ZSTD_strategy.ZSTD_fast) ? 0 : ((nuint)(1) << (int)cParams->chainLog); + nuint chainSize = (ZSTD_allocateChainTable(cParams->strategy, useRowMatchFinder, ((ms->dedicatedDictSearch != 0 && (forWho == ZSTD_resetTarget_e.ZSTD_resetTarget_CDict)) ? 1U : 0U))) != 0 ? ((nuint)(1) << (int)cParams->chainLog) : 0; nuint hSize = ((nuint)(1)) << (int)cParams->hashLog; uint hashLog3 = (uint)(((forWho == ZSTD_resetTarget_e.ZSTD_resetTarget_CCtx) && cParams->minMatch == 3) ? ((17) < (cParams->windowLog) ? (17) : (cParams->windowLog)) : 0); nuint h3Size = hashLog3 != 0 ? ((nuint)(1)) << (int)hashLog3 : 0; + assert(useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); if (forceResetIndex == ZSTD_indexResetPolicy_e.ZSTDirp_reset) { ZSTD_window_init(&ms->window); @@ -2291,6 +2484,28 @@ private static nuint ZSTD_reset_matchState(ZSTD_matchState_t* ms, ZSTD_cwksp* ws ms->opt.priceTable = (ZSTD_optimal_t*)(ZSTD_cwksp_reserve_aligned(ws, (uint)(((1 << 12) + 1)) * (nuint)(sizeof(ZSTD_optimal_t)))); } + if ((ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) != 0) + { + + { + nuint tagTableSize = hSize * (nuint)(2); + + ms->tagTable = (ushort*)(ZSTD_cwksp_reserve_aligned(ws, tagTableSize)); + if (ms->tagTable != null) + { + memset((void*)(ms->tagTable), (0), (tagTableSize)); + } + } + + + { + uint rowLog = (uint)(cParams->searchLog < 5 ? 4 : 5); + + assert(cParams->hashLog > rowLog); + ms->rowHashLog = cParams->hashLog - rowLog; + } + } + ms->cParams = *cParams; if ((ZSTD_cwksp_reserve_failed(ws)) != 0) { @@ -2305,33 +2520,52 @@ private static int ZSTD_indexTooCloseToMax(ZSTD_window_t w) return (((nuint)(w.nextSrc - w.@base) > (((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31)))) - (uint)((16 * (1 << 20))))) ? 1 : 0); } + /** ZSTD_dictTooBig(): + * When dictionaries are larger than ZSTD_CHUNKSIZE_MAX they can't be loaded in + * one go generically. So we ensure that in that case we reset the tables to zero, + * so that we can load as much of the dictionary as possible. + */ + private static int ZSTD_dictTooBig(nuint loadedDictSize) + { + return ((loadedDictSize > ((unchecked((uint)(-1))) - ((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31)))))) ? 1 : 0); + } + /*! ZSTD_resetCCtx_internal() : - note : `params` are assumed fully validated at this stage */ - private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s @params, ulong pledgedSrcSize, ZSTD_compResetPolicy_e crp, ZSTD_buffered_policy_e zbuff) + * @param loadedDictSize The size of the dictionary to be loaded + * into the context, if any. If no dictionary is used, or the + * dictionary is being attached / copied, then pass 0. + * note : `params` are assumed fully validated at this stage. + */ + private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s* @params, ulong pledgedSrcSize, nuint loadedDictSize, ZSTD_compResetPolicy_e crp, ZSTD_buffered_policy_e zbuff) { ZSTD_cwksp* ws = &zc->workspace; - assert((ERR_isError(ZSTD_checkCParams(@params.cParams))) == 0); + assert((ERR_isError(ZSTD_checkCParams(@params->cParams))) == 0); zc->isFirstBlock = 1; - if (@params.ldmParams.enableLdm != 0) + zc->appliedParams = *@params; + @params = &zc->appliedParams; + assert(@params->useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + if (@params->ldmParams.enableLdm != 0) { - ZSTD_ldm_adjustParameters(&@params.ldmParams, &@params.cParams); - assert(@params.ldmParams.hashLog >= @params.ldmParams.bucketSizeLog); - assert(@params.ldmParams.hashRateLog < 32); + ZSTD_ldm_adjustParameters(&zc->appliedParams.ldmParams, &@params->cParams); + assert(@params->ldmParams.hashLog >= @params->ldmParams.bucketSizeLog); + assert(@params->ldmParams.hashRateLog < 32); } { - nuint windowSize = ((1) > ((nuint)((((ulong)(1) << (int)@params.cParams.windowLog)) < (pledgedSrcSize) ? (((ulong)(1) << (int)@params.cParams.windowLog)) : (pledgedSrcSize))) ? (1) : ((nuint)((((ulong)(1) << (int)@params.cParams.windowLog)) < (pledgedSrcSize) ? (((ulong)(1) << (int)@params.cParams.windowLog)) : (pledgedSrcSize)))); + nuint windowSize = ((1) > ((nuint)((((ulong)(1) << (int)@params->cParams.windowLog)) < (pledgedSrcSize) ? (((ulong)(1) << (int)@params->cParams.windowLog)) : (pledgedSrcSize))) ? (1) : ((nuint)((((ulong)(1) << (int)@params->cParams.windowLog)) < (pledgedSrcSize) ? (((ulong)(1) << (int)@params->cParams.windowLog)) : (pledgedSrcSize)))); nuint blockSize = (nuint)((uint)(((1 << 17))) < (windowSize) ? ((1 << 17)) : (windowSize)); - uint divider = (uint)((@params.cParams.minMatch == 3) ? 3 : 4); + uint divider = (uint)((@params->cParams.minMatch == 3) ? 3 : 4); nuint maxNbSeq = blockSize / divider; - nuint buffOutSize = (zbuff == ZSTD_buffered_policy_e.ZSTDb_buffered && @params.outBufferMode == ZSTD_bufferMode_e.ZSTD_bm_buffered) ? ZSTD_compressBound(blockSize) + 1 : 0; - nuint buffInSize = (zbuff == ZSTD_buffered_policy_e.ZSTDb_buffered && @params.inBufferMode == ZSTD_bufferMode_e.ZSTD_bm_buffered) ? windowSize + blockSize : 0; - nuint maxNbLdmSeq = ZSTD_ldm_getMaxNbSeq(@params.ldmParams, blockSize); + nuint buffOutSize = (zbuff == ZSTD_buffered_policy_e.ZSTDb_buffered && @params->outBufferMode == ZSTD_bufferMode_e.ZSTD_bm_buffered) ? ZSTD_compressBound(blockSize) + 1 : 0; + nuint buffInSize = (zbuff == ZSTD_buffered_policy_e.ZSTDb_buffered && @params->inBufferMode == ZSTD_bufferMode_e.ZSTD_bm_buffered) ? windowSize + blockSize : 0; + nuint maxNbLdmSeq = ZSTD_ldm_getMaxNbSeq(@params->ldmParams, blockSize); int indexTooClose = ZSTD_indexTooCloseToMax(zc->blockState.matchState.window); - ZSTD_indexResetPolicy_e needsIndexReset = (indexTooClose == 0 && zc->initialized != 0) ? ZSTD_indexResetPolicy_e.ZSTDirp_continue : ZSTD_indexResetPolicy_e.ZSTDirp_reset; - nuint neededSpace = ZSTD_estimateCCtxSize_usingCCtxParams_internal(&@params.cParams, &@params.ldmParams, ((zc->staticSize != 0) ? 1 : 0), buffInSize, buffOutSize, pledgedSrcSize); + int dictTooBig = ZSTD_dictTooBig(loadedDictSize); + ZSTD_indexResetPolicy_e needsIndexReset = (indexTooClose != 0 || dictTooBig != 0 || zc->initialized == 0) ? ZSTD_indexResetPolicy_e.ZSTDirp_reset : ZSTD_indexResetPolicy_e.ZSTDirp_continue; + nuint neededSpace = ZSTD_estimateCCtxSize_usingCCtxParams_internal(&@params->cParams, &@params->ldmParams, ((zc->staticSize != 0) ? 1 : 0), @params->useRowMatchFinder, buffInSize, buffOutSize, pledgedSrcSize); + int resizeWorkspace; { @@ -2353,7 +2587,8 @@ private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s int workspaceTooSmall = ((ZSTD_cwksp_sizeof(ws) < neededSpace) ? 1 : 0); int workspaceWasteful = ZSTD_cwksp_check_wasteful(ws, neededSpace); - if (workspaceTooSmall != 0 || workspaceWasteful != 0) + resizeWorkspace = ((workspaceTooSmall != 0 || workspaceWasteful != 0) ? 1 : 0); + if (resizeWorkspace != 0) { if (zc->staticSize != 0) { @@ -2395,8 +2630,7 @@ private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s } ZSTD_cwksp_clear(ws); - zc->appliedParams = @params; - zc->blockState.matchState.cParams = @params.cParams; + zc->blockState.matchState.cParams = @params->cParams; zc->pledgedSrcSizePlusOne = pledgedSrcSize + 1; zc->consumedSrcSize = 0; zc->producedCSize = 0; @@ -2418,9 +2652,9 @@ private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s zc->inBuff = (sbyte*)(ZSTD_cwksp_reserve_buffer(ws, buffInSize)); zc->outBuffSize = buffOutSize; zc->outBuff = (sbyte*)(ZSTD_cwksp_reserve_buffer(ws, buffOutSize)); - if (@params.ldmParams.enableLdm != 0) + if (@params->ldmParams.enableLdm != 0) { - nuint numBuckets = ((nuint)(1)) << (int)(@params.ldmParams.hashLog - @params.ldmParams.bucketSizeLog); + nuint numBuckets = ((nuint)(1)) << (int)(@params->ldmParams.hashLog - @params->ldmParams.bucketSizeLog); zc->ldmState.bucketOffsets = ZSTD_cwksp_reserve_buffer(ws, numBuckets); memset((void*)(zc->ldmState.bucketOffsets), (0), (numBuckets)); @@ -2434,7 +2668,7 @@ private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s zc->seqStore.sequencesStart = (seqDef_s*)(ZSTD_cwksp_reserve_aligned(ws, maxNbSeq * (nuint)(sizeof(seqDef_s)))); { - nuint err_code = (ZSTD_reset_matchState(&zc->blockState.matchState, ws, &@params.cParams, crp, needsIndexReset, ZSTD_resetTarget_e.ZSTD_resetTarget_CCtx)); + nuint err_code = (ZSTD_reset_matchState(&zc->blockState.matchState, ws, &@params->cParams, @params->useRowMatchFinder, crp, needsIndexReset, ZSTD_resetTarget_e.ZSTD_resetTarget_CCtx)); if ((ERR_isError(err_code)) != 0) { @@ -2442,20 +2676,19 @@ private static nuint ZSTD_resetCCtx_internal(ZSTD_CCtx_s* zc, ZSTD_CCtx_params_s } } - if (@params.ldmParams.enableLdm != 0) + if (@params->ldmParams.enableLdm != 0) { - nuint ldmHSize = ((nuint)(1)) << (int)@params.ldmParams.hashLog; + nuint ldmHSize = ((nuint)(1)) << (int)@params->ldmParams.hashLog; zc->ldmState.hashTable = (ldmEntry_t*)(ZSTD_cwksp_reserve_aligned(ws, ldmHSize * (nuint)(sizeof(ldmEntry_t)))); memset((void*)(zc->ldmState.hashTable), (0), (ldmHSize * (nuint)(sizeof(ldmEntry_t)))); zc->ldmSequences = (rawSeq*)(ZSTD_cwksp_reserve_aligned(ws, maxNbLdmSeq * (nuint)(sizeof(rawSeq)))); zc->maxNbLdmSequences = maxNbLdmSeq; ZSTD_window_init(&zc->ldmState.window); - ZSTD_window_clear(&zc->ldmState.window); zc->ldmState.loadedDictEnd = 0; } - assert(ZSTD_cwksp_used(ws) >= neededSpace && ZSTD_cwksp_used(ws) <= neededSpace + 3); + assert((ZSTD_cwksp_estimated_space_within_bounds(ws, neededSpace, resizeWorkspace)) != 0); zc->initialized = 1; return 0; } @@ -2514,9 +2747,10 @@ private static nuint ZSTD_resetCCtx_byAttachingCDict(ZSTD_CCtx_s* cctx, ZSTD_CDi @params.cParams = ZSTD_adjustCParams_internal(adjusted_cdict_cParams, pledgedSrcSize, cdict->dictContentSize, ZSTD_cParamMode_e.ZSTD_cpm_attachDict); @params.cParams.windowLog = windowLog; + @params.useRowMatchFinder = cdict->useRowMatchFinder; { - nuint err_code = (ZSTD_resetCCtx_internal(cctx, @params, pledgedSrcSize, ZSTD_compResetPolicy_e.ZSTDcrp_makeClean, zbuff)); + nuint err_code = (ZSTD_resetCCtx_internal(cctx, &@params, pledgedSrcSize, 0, ZSTD_compResetPolicy_e.ZSTDcrp_makeClean, zbuff)); if ((ERR_isError(err_code)) != 0) { @@ -2567,9 +2801,10 @@ private static nuint ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx_s* cctx, ZSTD_CDict assert(windowLog != 0); @params.cParams = *cdict_cParams; @params.cParams.windowLog = windowLog; + @params.useRowMatchFinder = cdict->useRowMatchFinder; { - nuint err_code = (ZSTD_resetCCtx_internal(cctx, @params, pledgedSrcSize, ZSTD_compResetPolicy_e.ZSTDcrp_leaveDirty, zbuff)); + nuint err_code = (ZSTD_resetCCtx_internal(cctx, &@params, pledgedSrcSize, 0, ZSTD_compResetPolicy_e.ZSTDcrp_leaveDirty, zbuff)); if ((ERR_isError(err_code)) != 0) { @@ -2583,13 +2818,24 @@ private static nuint ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx_s* cctx, ZSTD_CDict } ZSTD_cwksp_mark_tables_dirty(&cctx->workspace); + assert(@params.useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); { - nuint chainSize = (cdict_cParams->strategy == ZSTD_strategy.ZSTD_fast) ? 0 : ((nuint)(1) << (int)cdict_cParams->chainLog); + nuint chainSize = (ZSTD_allocateChainTable(cdict_cParams->strategy, cdict->useRowMatchFinder, 0)) != 0 ? ((nuint)(1) << (int)cdict_cParams->chainLog) : 0; nuint hSize = (nuint)(1) << (int)cdict_cParams->hashLog; memcpy((void*)(cctx->blockState.matchState.hashTable), (void*)(cdict->matchState.hashTable), (hSize * (nuint)(sizeof(uint)))); - memcpy((void*)(cctx->blockState.matchState.chainTable), (void*)(cdict->matchState.chainTable), (chainSize * (nuint)(sizeof(uint)))); + if ((ZSTD_allocateChainTable(cctx->appliedParams.cParams.strategy, cctx->appliedParams.useRowMatchFinder, 0)) != 0) + { + memcpy((void*)(cctx->blockState.matchState.chainTable), (void*)(cdict->matchState.chainTable), (chainSize * (nuint)(sizeof(uint)))); + } + + if ((ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, cdict->useRowMatchFinder)) != 0) + { + nuint tagTableSize = hSize * (nuint)(2); + + memcpy((void*)(cctx->blockState.matchState.tagTable), (void*)(cdict->matchState.tagTable), (tagTableSize)); + } } @@ -2653,8 +2899,10 @@ private static nuint ZSTD_copyCCtx_internal(ZSTD_CCtx_s* dstCCtx, ZSTD_CCtx_s* s ZSTD_CCtx_params_s @params = dstCCtx->requestedParams; @params.cParams = srcCCtx->appliedParams.cParams; + assert(srcCCtx->appliedParams.useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + @params.useRowMatchFinder = srcCCtx->appliedParams.useRowMatchFinder; @params.fParams = fParams; - ZSTD_resetCCtx_internal(dstCCtx, @params, pledgedSrcSize, ZSTD_compResetPolicy_e.ZSTDcrp_leaveDirty, zbuff); + ZSTD_resetCCtx_internal(dstCCtx, &@params, pledgedSrcSize, 0, ZSTD_compResetPolicy_e.ZSTDcrp_leaveDirty, zbuff); assert(dstCCtx->appliedParams.cParams.windowLog == srcCCtx->appliedParams.cParams.windowLog); assert(dstCCtx->appliedParams.cParams.strategy == srcCCtx->appliedParams.cParams.strategy); assert(dstCCtx->appliedParams.cParams.hashLog == srcCCtx->appliedParams.cParams.hashLog); @@ -2665,7 +2913,7 @@ private static nuint ZSTD_copyCCtx_internal(ZSTD_CCtx_s* dstCCtx, ZSTD_CCtx_s* s ZSTD_cwksp_mark_tables_dirty(&dstCCtx->workspace); { - nuint chainSize = (srcCCtx->appliedParams.cParams.strategy == ZSTD_strategy.ZSTD_fast) ? 0 : ((nuint)(1) << (int)srcCCtx->appliedParams.cParams.chainLog); + nuint chainSize = (ZSTD_allocateChainTable(srcCCtx->appliedParams.cParams.strategy, srcCCtx->appliedParams.useRowMatchFinder, 0)) != 0 ? ((nuint)(1) << (int)srcCCtx->appliedParams.cParams.chainLog) : 0; nuint hSize = (nuint)(1) << (int)srcCCtx->appliedParams.cParams.hashLog; int h3log = (int)srcCCtx->blockState.matchState.hashLog3; nuint h3Size = h3log != 0 ? ((nuint)(1) << h3log) : 0; @@ -2779,7 +3027,7 @@ private static void ZSTD_reduceIndex(ZSTD_matchState_t* ms, ZSTD_CCtx_params_s* ZSTD_reduceTable(ms->hashTable, hSize, reducerValue); } - if (@params->cParams.strategy != ZSTD_strategy.ZSTD_fast) + if ((ZSTD_allocateChainTable(@params->cParams.strategy, @params->useRowMatchFinder, (uint)(ms->dedicatedDictSearch))) != 0) { uint chainSize = (uint)(1) << (int)@params->cParams.chainLog; @@ -2822,12 +3070,12 @@ public static void ZSTD_seqToCodes(seqStore_t* seqStorePtr) mlCodeTable[u] = (byte)(ZSTD_MLcode(mlv)); } - if (seqStorePtr->longLengthID == 1) + if (seqStorePtr->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_literalLength) { llCodeTable[seqStorePtr->longLengthPos] = 35; } - if (seqStorePtr->longLengthID == 2) + if (seqStorePtr->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_matchLength) { mlCodeTable[seqStorePtr->longLengthPos] = 52; } @@ -2842,10 +3090,136 @@ private static int ZSTD_useTargetCBlockSize(ZSTD_CCtx_params_s* cctxParams) return ((cctxParams->targetCBlockSize != 0) ? 1 : 0); } - /* ZSTD_entropyCompressSequences_internal(): - * actually compresses both literals and sequences */ + /* ZSTD_blockSplitterEnabled(): + * Returns if block splitting param is being used + * If used, compression will do best effort to split a block in order to improve compression ratio. + * Returns 1 if true, 0 otherwise. */ + private static int ZSTD_blockSplitterEnabled(ZSTD_CCtx_params_s* cctxParams) + { + return ((cctxParams->splitBlocks != 0) ? 1 : 0); + } + + /* ZSTD_buildSequencesStatistics(): + * Returns a ZSTD_symbolEncodingTypeStats_t, or a zstd error code in the `size` field. + * Modifies `nextEntropy` to have the appropriate values as a side effect. + * nbSeq must be greater than 0. + * + * entropyWkspSize must be of size at least ENTROPY_WORKSPACE_SIZE - (MaxSeq + 1)*sizeof(U32) + */ + private static ZSTD_symbolEncodingTypeStats_t ZSTD_buildSequencesStatistics(seqStore_t* seqStorePtr, nuint nbSeq, ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, byte* dst, byte* dstEnd, ZSTD_strategy strategy, uint* countWorkspace, void* entropyWorkspace, nuint entropyWkspSize) + { + byte* ostart = dst; + byte* oend = dstEnd; + byte* op = ostart; + uint* CTable_LitLength = (uint*)nextEntropy->litlengthCTable; + uint* CTable_OffsetBits = (uint*)nextEntropy->offcodeCTable; + uint* CTable_MatchLength = (uint*)nextEntropy->matchlengthCTable; + byte* ofCodeTable = seqStorePtr->ofCode; + byte* llCodeTable = seqStorePtr->llCode; + byte* mlCodeTable = seqStorePtr->mlCode; + ZSTD_symbolEncodingTypeStats_t stats; + var _ = &stats; + + stats.lastCountSize = 0; + ZSTD_seqToCodes(seqStorePtr); + assert(op <= oend); + assert(nbSeq != 0); + + { + uint max = 35; + nuint mostFrequent = HIST_countFast_wksp(countWorkspace, &max, (void*)llCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); + + nextEntropy->litlength_repeatMode = prevEntropy->litlength_repeatMode; + stats.LLtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->litlength_repeatMode, countWorkspace, max, mostFrequent, nbSeq, 9, (uint*)prevEntropy->litlengthCTable, (short*)LL_defaultNorm, LL_defaultNormLog, ZSTD_defaultPolicy_e.ZSTD_defaultAllowed, strategy)); + assert(symbolEncodingType_e.set_basic < symbolEncodingType_e.set_compressed && symbolEncodingType_e.set_rle < symbolEncodingType_e.set_compressed); + assert(!(stats.LLtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->litlength_repeatMode != FSE_repeat.FSE_repeat_none)); + + { + nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_LitLength, 9, (symbolEncodingType_e)(stats.LLtype), countWorkspace, max, llCodeTable, nbSeq, (short*)LL_defaultNorm, LL_defaultNormLog, 35, (uint*)prevEntropy->litlengthCTable, (nuint)(1316), entropyWorkspace, entropyWkspSize); + + if ((ERR_isError(countSize)) != 0) + { + stats.size = countSize; + return stats; + } + + if (stats.LLtype == (uint)symbolEncodingType_e.set_compressed) + { + stats.lastCountSize = countSize; + } + + op += countSize; + assert(op <= oend); + } + } + + + { + uint max = 31; + nuint mostFrequent = HIST_countFast_wksp(countWorkspace, &max, (void*)ofCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); + ZSTD_defaultPolicy_e defaultPolicy = (max <= 28) ? ZSTD_defaultPolicy_e.ZSTD_defaultAllowed : ZSTD_defaultPolicy_e.ZSTD_defaultDisallowed; + + nextEntropy->offcode_repeatMode = prevEntropy->offcode_repeatMode; + stats.Offtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->offcode_repeatMode, countWorkspace, max, mostFrequent, nbSeq, 8, (uint*)prevEntropy->offcodeCTable, (short*)OF_defaultNorm, OF_defaultNormLog, defaultPolicy, strategy)); + assert(!(stats.Offtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->offcode_repeatMode != FSE_repeat.FSE_repeat_none)); + + { + nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_OffsetBits, 8, (symbolEncodingType_e)(stats.Offtype), countWorkspace, max, ofCodeTable, nbSeq, (short*)OF_defaultNorm, OF_defaultNormLog, 28, (uint*)prevEntropy->offcodeCTable, (nuint)(772), entropyWorkspace, entropyWkspSize); + + if ((ERR_isError(countSize)) != 0) + { + stats.size = countSize; + return stats; + } + + if (stats.Offtype == (uint)symbolEncodingType_e.set_compressed) + { + stats.lastCountSize = countSize; + } + + op += countSize; + assert(op <= oend); + } + } + + + { + uint max = 52; + nuint mostFrequent = HIST_countFast_wksp(countWorkspace, &max, (void*)mlCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); + + nextEntropy->matchlength_repeatMode = prevEntropy->matchlength_repeatMode; + stats.MLtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->matchlength_repeatMode, countWorkspace, max, mostFrequent, nbSeq, 9, (uint*)prevEntropy->matchlengthCTable, (short*)ML_defaultNorm, ML_defaultNormLog, ZSTD_defaultPolicy_e.ZSTD_defaultAllowed, strategy)); + assert(!(stats.MLtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->matchlength_repeatMode != FSE_repeat.FSE_repeat_none)); + + { + nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_MatchLength, 9, (symbolEncodingType_e)(stats.MLtype), countWorkspace, max, mlCodeTable, nbSeq, (short*)ML_defaultNorm, ML_defaultNormLog, 52, (uint*)prevEntropy->matchlengthCTable, (nuint)(1452), entropyWorkspace, entropyWkspSize); + + if ((ERR_isError(countSize)) != 0) + { + stats.size = countSize; + return stats; + } + + if (stats.MLtype == (uint)symbolEncodingType_e.set_compressed) + { + stats.lastCountSize = countSize; + } + + op += countSize; + assert(op <= oend); + } + } + + stats.size = (nuint)(op - ostart); + return stats; + } + + /* ZSTD_entropyCompressSeqStore_internal(): + * compresses both literals and sequences + * Returns compressed size of block, or a zstd error. + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nuint ZSTD_entropyCompressSequences_internal(seqStore_t* seqStorePtr, ZSTD_entropyCTables_t* prevEntropy, ZSTD_entropyCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, void* dst, nuint dstCapacity, void* entropyWorkspace, nuint entropyWkspSize, int bmi2) + private static nuint ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, ZSTD_entropyCTables_t* prevEntropy, ZSTD_entropyCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, void* dst, nuint dstCapacity, void* entropyWorkspace, nuint entropyWkspSize, int bmi2) { int longOffsets = ((cctxParams->cParams.windowLog > ((uint)(MEM_32bits ? 25 : 57))) ? 1 : 0); ZSTD_strategy strategy = cctxParams->cParams.strategy; @@ -2853,17 +3227,15 @@ private static nuint ZSTD_entropyCompressSequences_internal(seqStore_t* seqStore uint* CTable_LitLength = (uint*)nextEntropy->fse.litlengthCTable; uint* CTable_OffsetBits = (uint*)nextEntropy->fse.offcodeCTable; uint* CTable_MatchLength = (uint*)nextEntropy->fse.matchlengthCTable; - uint LLtype, Offtype, MLtype; seqDef_s* sequences = seqStorePtr->sequencesStart; + nuint nbSeq = (nuint)(seqStorePtr->sequences - seqStorePtr->sequencesStart); byte* ofCodeTable = seqStorePtr->ofCode; byte* llCodeTable = seqStorePtr->llCode; byte* mlCodeTable = seqStorePtr->mlCode; byte* ostart = (byte*)(dst); byte* oend = ostart + dstCapacity; byte* op = ostart; - nuint nbSeq = (nuint)(seqStorePtr->sequences - seqStorePtr->sequencesStart); - byte* seqHead; - byte* lastNCount = (byte*)null; + nuint lastCountSize; entropyWorkspace = count + (((35) > (52) ? (35) : (52)) + 1); entropyWkspSize -= (uint)((((35) > (52) ? (35) : (52)) + 1)) * (nuint)(sizeof(uint)); @@ -2917,138 +3289,57 @@ private static nuint ZSTD_entropyCompressSequences_internal(seqStore_t* seqStore return (nuint)(op - ostart); } - seqHead = op++; - assert(op <= oend); - ZSTD_seqToCodes(seqStorePtr); { - uint max = 35; - nuint mostFrequent = HIST_countFast_wksp(count, &max, (void*)llCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); + ZSTD_symbolEncodingTypeStats_t stats; + byte* seqHead = op++; - nextEntropy->fse.litlength_repeatMode = prevEntropy->fse.litlength_repeatMode; - LLtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->fse.litlength_repeatMode, count, max, mostFrequent, nbSeq, 9, (uint*)prevEntropy->fse.litlengthCTable, (short*)LL_defaultNorm, LL_defaultNormLog, ZSTD_defaultPolicy_e.ZSTD_defaultAllowed, strategy)); - assert(symbolEncodingType_e.set_basic < symbolEncodingType_e.set_compressed && symbolEncodingType_e.set_rle < symbolEncodingType_e.set_compressed); - assert(!(LLtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->fse.litlength_repeatMode != FSE_repeat.FSE_repeat_none)); + stats = ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq, &prevEntropy->fse, &nextEntropy->fse, op, oend, strategy, count, entropyWorkspace, entropyWkspSize); { - nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_LitLength, 9, (symbolEncodingType_e)(LLtype), count, max, llCodeTable, nbSeq, (short*)LL_defaultNorm, LL_defaultNormLog, 35, (uint*)prevEntropy->fse.litlengthCTable, (nuint)(1316), entropyWorkspace, entropyWkspSize); - - - { - nuint err_code = (countSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } + nuint err_code = (stats.size); - if (LLtype == (uint)symbolEncodingType_e.set_compressed) + if ((ERR_isError(err_code)) != 0) { - lastNCount = op; + return err_code; } - - op += countSize; - assert(op <= oend); } + + *seqHead = (byte)((stats.LLtype << 6) + (stats.Offtype << 4) + (stats.MLtype << 2)); + lastCountSize = stats.lastCountSize; + op += stats.size; } { - uint max = 31; - nuint mostFrequent = HIST_countFast_wksp(count, &max, (void*)ofCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); - ZSTD_defaultPolicy_e defaultPolicy = (max <= 28) ? ZSTD_defaultPolicy_e.ZSTD_defaultAllowed : ZSTD_defaultPolicy_e.ZSTD_defaultDisallowed; + nuint bitstreamSize = ZSTD_encodeSequences((void*)op, (nuint)(oend - op), CTable_MatchLength, mlCodeTable, CTable_OffsetBits, ofCodeTable, CTable_LitLength, llCodeTable, sequences, nbSeq, longOffsets, bmi2); - nextEntropy->fse.offcode_repeatMode = prevEntropy->fse.offcode_repeatMode; - Offtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->fse.offcode_repeatMode, count, max, mostFrequent, nbSeq, 8, (uint*)prevEntropy->fse.offcodeCTable, (short*)OF_defaultNorm, OF_defaultNormLog, defaultPolicy, strategy)); - assert(!(Offtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->fse.offcode_repeatMode != FSE_repeat.FSE_repeat_none)); { - nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_OffsetBits, 8, (symbolEncodingType_e)(Offtype), count, max, ofCodeTable, nbSeq, (short*)OF_defaultNorm, OF_defaultNormLog, 28, (uint*)prevEntropy->fse.offcodeCTable, (nuint)(772), entropyWorkspace, entropyWkspSize); - - - { - nuint err_code = (countSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } + nuint err_code = (bitstreamSize); - if (Offtype == (uint)symbolEncodingType_e.set_compressed) + if ((ERR_isError(err_code)) != 0) { - lastNCount = op; + return err_code; } + } - op += countSize; - assert(op <= oend); + op += bitstreamSize; + assert(op <= oend); + if (lastCountSize != 0 && (lastCountSize + bitstreamSize) < 4) + { + assert(lastCountSize + bitstreamSize == 3); + return 0; } } + return (nuint)(op - ostart); + } - { - uint max = 52; - nuint mostFrequent = HIST_countFast_wksp(count, &max, (void*)mlCodeTable, nbSeq, entropyWorkspace, entropyWkspSize); - - nextEntropy->fse.matchlength_repeatMode = prevEntropy->fse.matchlength_repeatMode; - MLtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->fse.matchlength_repeatMode, count, max, mostFrequent, nbSeq, 9, (uint*)prevEntropy->fse.matchlengthCTable, (short*)ML_defaultNorm, ML_defaultNormLog, ZSTD_defaultPolicy_e.ZSTD_defaultAllowed, strategy)); - assert(!(MLtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->fse.matchlength_repeatMode != FSE_repeat.FSE_repeat_none)); - - { - nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_MatchLength, 9, (symbolEncodingType_e)(MLtype), count, max, mlCodeTable, nbSeq, (short*)ML_defaultNorm, ML_defaultNormLog, 52, (uint*)prevEntropy->fse.matchlengthCTable, (nuint)(1452), entropyWorkspace, entropyWkspSize); - - - { - nuint err_code = (countSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - if (MLtype == (uint)symbolEncodingType_e.set_compressed) - { - lastNCount = op; - } - - op += countSize; - assert(op <= oend); - } - } - - *seqHead = (byte)((LLtype << 6) + (Offtype << 4) + (MLtype << 2)); - - { - nuint bitstreamSize = ZSTD_encodeSequences((void*)op, (nuint)(oend - op), CTable_MatchLength, mlCodeTable, CTable_OffsetBits, ofCodeTable, CTable_LitLength, llCodeTable, sequences, nbSeq, longOffsets, bmi2); - - - { - nuint err_code = (bitstreamSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - op += bitstreamSize; - assert(op <= oend); - if (lastNCount != null && (op - lastNCount) < 4) - { - assert(op - lastNCount == 3); - return 0; - } - } - - return (nuint)(op - ostart); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nuint ZSTD_entropyCompressSequences(seqStore_t* seqStorePtr, ZSTD_entropyCTables_t* prevEntropy, ZSTD_entropyCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, void* dst, nuint dstCapacity, nuint srcSize, void* entropyWorkspace, nuint entropyWkspSize, int bmi2) - { - nuint cSize = ZSTD_entropyCompressSequences_internal(seqStorePtr, prevEntropy, nextEntropy, cctxParams, dst, dstCapacity, entropyWorkspace, entropyWkspSize, bmi2); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_entropyCompressSeqStore(seqStore_t* seqStorePtr, ZSTD_entropyCTables_t* prevEntropy, ZSTD_entropyCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, void* dst, nuint dstCapacity, nuint srcSize, void* entropyWorkspace, nuint entropyWkspSize, int bmi2) + { + nuint cSize = ZSTD_entropyCompressSeqStore_internal(seqStorePtr, prevEntropy, nextEntropy, cctxParams, dst, dstCapacity, entropyWorkspace, entropyWkspSize, bmi2); if (cSize == 0) { @@ -3086,13 +3377,24 @@ private static nuint ZSTD_entropyCompressSequences(seqStore_t* seqStorePtr, ZSTD /* ZSTD_selectBlockCompressor() : * Not static, but internal use only (used by long distance matcher) * assumption : strat is a valid strategy */ - public static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_dictMode_e dictMode) + public static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, ZSTD_dictMode_e dictMode) { ZSTD_blockCompressor selectedCompressor; assert((ZSTD_cParam_withinBounds(ZSTD_cParameter.ZSTD_c_strategy, (int)strat)) != 0); - selectedCompressor = blockCompressor[(int)(dictMode)][(int)(strat)]; + if ((ZSTD_rowMatchFinderUsed(strat, useRowMatchFinder)) != 0) + { + + + assert(useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + selectedCompressor = rowBasedBlockCompressors[(int)(dictMode)][(int)(strat) - (int)(ZSTD_strategy.ZSTD_greedy)]; + } + else + { + selectedCompressor = blockCompressor[(int)(dictMode)][(int)(strat)]; + } + assert(selectedCompressor != null); return selectedCompressor; } @@ -3107,7 +3409,7 @@ public static void ZSTD_resetSeqStore(seqStore_t* ssPtr) { ssPtr->lit = ssPtr->litStart; ssPtr->sequences = ssPtr->sequencesStart; - ssPtr->longLengthID = 0; + ssPtr->longLengthType = ZSTD_longLengthType_e.ZSTD_llt_none; } private static nuint ZSTD_buildSeqStore(ZSTD_CCtx_s* zc, void* src, nuint srcSize) @@ -3142,7 +3444,7 @@ private static nuint ZSTD_buildSeqStore(ZSTD_CCtx_s* zc, void* src, nuint srcSiz if ((nuint)(sizeof(nint)) == 8) { - assert(istart - @base < (nint)(unchecked((uint)(-1)))); + assert(istart - @base < unchecked((nint)(unchecked((uint)(-1))))); } if (curr > ms->nextToUpdate + 384) @@ -3169,7 +3471,7 @@ private static nuint ZSTD_buildSeqStore(ZSTD_CCtx_s* zc, void* src, nuint srcSiz if (zc->externSeqStore.pos < zc->externSeqStore.size) { assert(zc->appliedParams.ldmParams.enableLdm == 0); - lastLLSize = ZSTD_ldm_blockCompress(&zc->externSeqStore, ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); + lastLLSize = ZSTD_ldm_blockCompress(&zc->externSeqStore, ms, &zc->seqStore, zc->blockState.nextCBlock->rep, zc->appliedParams.useRowMatchFinder, src, srcSize); assert(zc->externSeqStore.pos <= zc->externSeqStore.size); } else if (zc->appliedParams.ldmParams.enableLdm != 0) @@ -3188,12 +3490,12 @@ private static nuint ZSTD_buildSeqStore(ZSTD_CCtx_s* zc, void* src, nuint srcSiz } } - lastLLSize = ZSTD_ldm_blockCompress(&ldmSeqStore, ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); + lastLLSize = ZSTD_ldm_blockCompress(&ldmSeqStore, ms, &zc->seqStore, zc->blockState.nextCBlock->rep, zc->appliedParams.useRowMatchFinder, src, srcSize); assert(ldmSeqStore.pos == ldmSeqStore.size); } else { - ZSTD_blockCompressor blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, dictMode); + ZSTD_blockCompressor blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, zc->appliedParams.useRowMatchFinder, dictMode); ms->ldmSeqStore = null; lastLLSize = blockCompressor(ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); @@ -3234,11 +3536,11 @@ private static void ZSTD_copyBlockSequences(ZSTD_CCtx_s* zc) outSeqs[i].rep = 0; if (i == seqStore->longLengthPos) { - if (seqStore->longLengthID == 1) + if (seqStore->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_literalLength) { outSeqs[i].litLength += 0x10000; } - else if (seqStore->longLengthID == 2) + else if (seqStore->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_matchLength) { outSeqs[i].matchLength += 0x10000; } @@ -3288,120 +3590,917 @@ private static void ZSTD_copyBlockSequences(ZSTD_CCtx_s* zc) * zc can be used to insert custom compression params. * This function invokes ZSTD_compress2 * - * The output of this function can be fed into ZSTD_compressSequences() with CCtx - * setting of ZSTD_c_blockDelimiters as ZSTD_sf_explicitBlockDelimiters - * @return : number of sequences generated + * The output of this function can be fed into ZSTD_compressSequences() with CCtx + * setting of ZSTD_c_blockDelimiters as ZSTD_sf_explicitBlockDelimiters + * @return : number of sequences generated + */ + public static nuint ZSTD_generateSequences(ZSTD_CCtx_s* zc, ZSTD_Sequence* outSeqs, nuint outSeqsSize, void* src, nuint srcSize) + { + nuint dstCapacity = ZSTD_compressBound(srcSize); + void* dst = ZSTD_customMalloc(dstCapacity, ZSTD_defaultCMem); + SeqCollector seqCollector; + + if (dst == null) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_memory_allocation))); + } + + seqCollector.collectSequences = 1; + seqCollector.seqStart = outSeqs; + seqCollector.seqIndex = 0; + seqCollector.maxSequences = outSeqsSize; + zc->seqCollector = seqCollector; + ZSTD_compress2(zc, dst, dstCapacity, src, srcSize); + ZSTD_customFree(dst, ZSTD_defaultCMem); + return zc->seqCollector.seqIndex; + } + + /*! ZSTD_mergeBlockDelimiters() : + * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals + * by merging them into into the literals of the next sequence. + * + * As such, the final generated result has no explicit representation of block boundaries, + * and the final last literals segment is not represented in the sequences. + * + * The output of this function can be fed into ZSTD_compressSequences() with CCtx + * setting of ZSTD_c_blockDelimiters as ZSTD_sf_noBlockDelimiters + * @return : number of sequences left after merging + */ + public static nuint ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, nuint seqsSize) + { + nuint @in = 0; + nuint @out = 0; + + for (; @in < seqsSize; ++@in) + { + if (sequences[@in].offset == 0 && sequences[@in].matchLength == 0) + { + if (@in != seqsSize - 1) + { + sequences[@in + 1].litLength += sequences[@in].litLength; + } + } + else + { + sequences[@out] = sequences[@in]; + ++@out; + } + } + + return @out; + } + + /* Unrolled loop to read four size_ts of input at a time. Returns 1 if is RLE, 0 if not. */ + private static int ZSTD_isRLE(byte* src, nuint length) + { + byte* ip = src; + byte value = ip[0]; + nuint valueST = (nuint)((ulong)(value) * 0x0101010101010101UL); + nuint unrollSize = (nuint)(sizeof(nuint)) * 4; + nuint unrollMask = unrollSize - 1; + nuint prefixLength = length & unrollMask; + nuint i; + nuint u; + + if (length == 1) + { + return 1; + } + + if (prefixLength != 0 && ZSTD_count(ip + 1, ip, ip + prefixLength) != prefixLength - 1) + { + return 0; + } + + for (i = prefixLength; i != length; i += unrollSize) + { + for (u = 0; u < unrollSize; u += (nuint)(sizeof(nuint))) + { + if (MEM_readST((void*)(ip + i + u)) != valueST) + { + return 0; + } + } + } + + return 1; + } + + /* Returns true if the given block may be RLE. + * This is just a heuristic based on the compressibility. + * It may return both false positives and false negatives. + */ + private static int ZSTD_maybeRLE(seqStore_t* seqStore) + { + nuint nbSeqs = (nuint)(seqStore->sequences - seqStore->sequencesStart); + nuint nbLits = (nuint)(seqStore->lit - seqStore->litStart); + + return ((nbSeqs < 4 && nbLits < 10) ? 1 : 0); + } + + private static void ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* bs) + { + ZSTD_compressedBlockState_t* tmp = bs->prevCBlock; + + bs->prevCBlock = bs->nextCBlock; + bs->nextCBlock = tmp; + } + + /* Writes the block header */ + private static void writeBlockHeader(void* op, nuint cSize, nuint blockSize, uint lastBlock) + { + uint cBlockHeader = cSize == 1 ? lastBlock + (((uint)(blockType_e.bt_rle)) << 1) + (uint)(blockSize << 3) : lastBlock + (((uint)(blockType_e.bt_compressed)) << 1) + (uint)(cSize << 3); + + MEM_writeLE24(op, cBlockHeader); + } + + /** ZSTD_buildBlockEntropyStats_literals() : + * Builds entropy for the literals. + * Stores literals block type (raw, rle, compressed, repeat) and + * huffman description table to hufMetadata. + * Requires ENTROPY_WORKSPACE_SIZE workspace + * @return : size of huffman description table or error code */ + private static nuint ZSTD_buildBlockEntropyStats_literals(void* src, nuint srcSize, ZSTD_hufCTables_t* prevHuf, ZSTD_hufCTables_t* nextHuf, ZSTD_hufCTablesMetadata_t* hufMetadata, int disableLiteralsCompression, void* workspace, nuint wkspSize) + { + byte* wkspStart = (byte*)(workspace); + byte* wkspEnd = wkspStart + wkspSize; + byte* countWkspStart = wkspStart; + uint* countWksp = (uint*)(workspace); + nuint countWkspSize = (uint)((255 + 1)) * (nuint)(4); + byte* nodeWksp = countWkspStart + countWkspSize; + nuint nodeWkspSize = (nuint)(wkspEnd - nodeWksp); + uint maxSymbolValue = 255; + uint huffLog = 11; + HUF_repeat repeat = prevHuf->repeatMode; + + memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); + if (disableLiteralsCompression != 0) + { + hufMetadata->hType = symbolEncodingType_e.set_basic; + return 0; + } + + + { + nuint minLitSize = (nuint)((prevHuf->repeatMode == HUF_repeat.HUF_repeat_valid) ? 6 : 63); + + if (srcSize <= minLitSize) + { + hufMetadata->hType = symbolEncodingType_e.set_basic; + return 0; + } + } + + + { + nuint largest = HIST_count_wksp(countWksp, &maxSymbolValue, (void*)(byte*)(src), srcSize, workspace, wkspSize); + + + { + nuint err_code = (largest); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + if (largest == srcSize) + { + hufMetadata->hType = symbolEncodingType_e.set_rle; + return 0; + } + + if (largest <= (srcSize >> 7) + 4) + { + hufMetadata->hType = symbolEncodingType_e.set_basic; + return 0; + } + } + + if (repeat == HUF_repeat.HUF_repeat_check && (HUF_validateCTable((HUF_CElt_s*)(prevHuf->CTable), countWksp, maxSymbolValue)) == 0) + { + repeat = HUF_repeat.HUF_repeat_none; + } + + memset((void*)(nextHuf->CTable), (0), ((nuint)(sizeof(HUF_CElt_s) * 256))); + huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); + + { + nuint maxBits = HUF_buildCTable_wksp((HUF_CElt_s*)(nextHuf->CTable), countWksp, maxSymbolValue, huffLog, (void*)nodeWksp, nodeWkspSize); + + + { + nuint err_code = (maxBits); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + huffLog = (uint)(maxBits); + + { + nuint newCSize = HUF_estimateCompressedSize((HUF_CElt_s*)(nextHuf->CTable), countWksp, maxSymbolValue); + nuint hSize = HUF_writeCTable_wksp((void*)hufMetadata->hufDesBuffer, (nuint)(128), (HUF_CElt_s*)(nextHuf->CTable), maxSymbolValue, huffLog, (void*)nodeWksp, nodeWkspSize); + + if (repeat != HUF_repeat.HUF_repeat_none) + { + nuint oldCSize = HUF_estimateCompressedSize((HUF_CElt_s*)(prevHuf->CTable), countWksp, maxSymbolValue); + + if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) + { + memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); + hufMetadata->hType = symbolEncodingType_e.set_repeat; + return 0; + } + } + + if (newCSize + hSize >= srcSize) + { + memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); + hufMetadata->hType = symbolEncodingType_e.set_basic; + return 0; + } + + hufMetadata->hType = symbolEncodingType_e.set_compressed; + nextHuf->repeatMode = HUF_repeat.HUF_repeat_check; + return hSize; + } + } + } + + /* ZSTD_buildDummySequencesStatistics(): + * Returns a ZSTD_symbolEncodingTypeStats_t with all encoding types as set_basic, + * and updates nextEntropy to the appropriate repeatMode. + */ + private static ZSTD_symbolEncodingTypeStats_t ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy) + { + ZSTD_symbolEncodingTypeStats_t stats = new ZSTD_symbolEncodingTypeStats_t + { + LLtype = (uint)symbolEncodingType_e.set_basic, + Offtype = (uint)symbolEncodingType_e.set_basic, + MLtype = (uint)symbolEncodingType_e.set_basic, + size = 0, + lastCountSize = 0, + }; + + nextEntropy->litlength_repeatMode = FSE_repeat.FSE_repeat_none; + nextEntropy->offcode_repeatMode = FSE_repeat.FSE_repeat_none; + nextEntropy->matchlength_repeatMode = FSE_repeat.FSE_repeat_none; + return stats; + } + + /** ZSTD_buildBlockEntropyStats_sequences() : + * Builds entropy for the sequences. + * Stores symbol compression modes and fse table to fseMetadata. + * Requires ENTROPY_WORKSPACE_SIZE wksp. + * @return : size of fse tables or error code */ + private static nuint ZSTD_buildBlockEntropyStats_sequences(seqStore_t* seqStorePtr, ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, ZSTD_fseCTablesMetadata_t* fseMetadata, void* workspace, nuint wkspSize) + { + ZSTD_strategy strategy = cctxParams->cParams.strategy; + nuint nbSeq = (nuint)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + byte* ostart = (byte*)fseMetadata->fseTablesBuffer; + byte* oend = ostart + (nuint)(133); + byte* op = ostart; + uint* countWorkspace = (uint*)(workspace); + uint* entropyWorkspace = countWorkspace + (((35) > (52) ? (35) : (52)) + 1); + nuint entropyWorkspaceSize = wkspSize - (uint)((((35) > (52) ? (35) : (52)) + 1)) * (nuint)(4); + ZSTD_symbolEncodingTypeStats_t stats; + + stats = nbSeq != 0 ? ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq, prevEntropy, nextEntropy, op, oend, strategy, countWorkspace, (void*)entropyWorkspace, entropyWorkspaceSize) : ZSTD_buildDummySequencesStatistics(nextEntropy); + + { + nuint err_code = (stats.size); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + fseMetadata->llType = (symbolEncodingType_e)(stats.LLtype); + fseMetadata->ofType = (symbolEncodingType_e)(stats.Offtype); + fseMetadata->mlType = (symbolEncodingType_e)(stats.MLtype); + fseMetadata->lastCountSize = stats.lastCountSize; + return stats.size; + } + + /** ZSTD_buildBlockEntropyStats() : + * Builds entropy for the block. + * Requires workspace size ENTROPY_WORKSPACE_SIZE + * + * @return : 0 on success or error code + */ + public static nuint ZSTD_buildBlockEntropyStats(seqStore_t* seqStorePtr, ZSTD_entropyCTables_t* prevEntropy, ZSTD_entropyCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, ZSTD_entropyCTablesMetadata_t* entropyMetadata, void* workspace, nuint wkspSize) + { + nuint litSize = (nuint)(seqStorePtr->lit - seqStorePtr->litStart); + + entropyMetadata->hufMetadata.hufDesSize = ZSTD_buildBlockEntropyStats_literals((void*)seqStorePtr->litStart, litSize, &prevEntropy->huf, &nextEntropy->huf, &entropyMetadata->hufMetadata, ZSTD_disableLiteralsCompression(cctxParams), workspace, wkspSize); + + { + nuint err_code = (entropyMetadata->hufMetadata.hufDesSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + entropyMetadata->fseMetadata.fseTablesSize = ZSTD_buildBlockEntropyStats_sequences(seqStorePtr, &prevEntropy->fse, &nextEntropy->fse, cctxParams, &entropyMetadata->fseMetadata, workspace, wkspSize); + + { + nuint err_code = (entropyMetadata->fseMetadata.fseTablesSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + return 0; + } + + /* Returns the size estimate for the literals section (header + content) of a block */ + private static nuint ZSTD_estimateBlockSize_literal(byte* literals, nuint litSize, ZSTD_hufCTables_t* huf, ZSTD_hufCTablesMetadata_t* hufMetadata, void* workspace, nuint wkspSize, int writeEntropy) + { + uint* countWksp = (uint*)(workspace); + uint maxSymbolValue = 255; + nuint literalSectionHeaderSize = (nuint)(3 + ((litSize >= (uint)(1 * (1 << 10))) ? 1 : 0) + ((litSize >= (uint)(16 * (1 << 10))) ? 1 : 0)); + uint singleStream = ((litSize < 256) ? 1U : 0U); + + if (hufMetadata->hType == symbolEncodingType_e.set_basic) + { + return litSize; + } + else if (hufMetadata->hType == symbolEncodingType_e.set_rle) + { + return 1; + } + else if (hufMetadata->hType == symbolEncodingType_e.set_compressed || hufMetadata->hType == symbolEncodingType_e.set_repeat) + { + nuint largest = HIST_count_wksp(countWksp, &maxSymbolValue, (void*)(byte*)(literals), litSize, workspace, wkspSize); + + if ((ERR_isError(largest)) != 0) + { + return litSize; + } + + + { + nuint cLitSizeEstimate = HUF_estimateCompressedSize((HUF_CElt_s*)(huf->CTable), countWksp, maxSymbolValue); + + if (writeEntropy != 0) + { + cLitSizeEstimate += hufMetadata->hufDesSize; + } + + if (singleStream == 0) + { + cLitSizeEstimate += 6; + } + + return cLitSizeEstimate + literalSectionHeaderSize; + } + } + + assert(0 != 0); + return 0; + } + + /* Returns the size estimate for the FSE-compressed symbols (of, ml, ll) of a block */ + private static nuint ZSTD_estimateBlockSize_symbolType(symbolEncodingType_e type, byte* codeTable, nuint nbSeq, uint maxCode, uint* fseCTable, uint* additionalBits, short* defaultNorm, uint defaultNormLog, uint defaultMax, void* workspace, nuint wkspSize) + { + uint* countWksp = (uint*)(workspace); + byte* ctp = codeTable; + byte* ctStart = ctp; + byte* ctEnd = ctStart + nbSeq; + nuint cSymbolTypeSizeEstimateInBits = 0; + uint max = maxCode; + + HIST_countFast_wksp(countWksp, &max, (void*)codeTable, nbSeq, workspace, wkspSize); + if (type == symbolEncodingType_e.set_basic) + { + assert(max <= defaultMax); + cSymbolTypeSizeEstimateInBits = ZSTD_crossEntropyCost(defaultNorm, defaultNormLog, countWksp, max); + } + else if (type == symbolEncodingType_e.set_rle) + { + cSymbolTypeSizeEstimateInBits = 0; + } + else if (type == symbolEncodingType_e.set_compressed || type == symbolEncodingType_e.set_repeat) + { + cSymbolTypeSizeEstimateInBits = ZSTD_fseBitCost(fseCTable, countWksp, max); + } + + if ((ERR_isError(cSymbolTypeSizeEstimateInBits)) != 0) + { + return nbSeq * 10; + } + + while (ctp < ctEnd) + { + if (additionalBits != null) + { + cSymbolTypeSizeEstimateInBits += additionalBits[*ctp]; + } + else + { + cSymbolTypeSizeEstimateInBits += *ctp; + } + + ctp++; + } + + return cSymbolTypeSizeEstimateInBits >> 3; + } + + /* Returns the size estimate for the sequences section (header + content) of a block */ + private static nuint ZSTD_estimateBlockSize_sequences(byte* ofCodeTable, byte* llCodeTable, byte* mlCodeTable, nuint nbSeq, ZSTD_fseCTables_t* fseTables, ZSTD_fseCTablesMetadata_t* fseMetadata, void* workspace, nuint wkspSize, int writeEntropy) + { + nuint sequencesSectionHeaderSize = (nuint)(1 + 1 + ((nbSeq >= 128) ? 1 : 0) + ((nbSeq >= 0x7F00) ? 1 : 0)); + nuint cSeqSizeEstimate = 0; + + cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->ofType, ofCodeTable, nbSeq, 31, (uint*)fseTables->offcodeCTable, (uint*)null, (short*)OF_defaultNorm, OF_defaultNormLog, 28, workspace, wkspSize); + cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->llType, llCodeTable, nbSeq, 35, (uint*)fseTables->litlengthCTable, (uint*)LL_bits, (short*)LL_defaultNorm, LL_defaultNormLog, 35, workspace, wkspSize); + cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->mlType, mlCodeTable, nbSeq, 52, (uint*)fseTables->matchlengthCTable, (uint*)ML_bits, (short*)ML_defaultNorm, ML_defaultNormLog, 52, workspace, wkspSize); + if (writeEntropy != 0) + { + cSeqSizeEstimate += fseMetadata->fseTablesSize; + } + + return cSeqSizeEstimate + sequencesSectionHeaderSize; + } + + /* Returns the size estimate for a given stream of literals, of, ll, ml */ + private static nuint ZSTD_estimateBlockSize(byte* literals, nuint litSize, byte* ofCodeTable, byte* llCodeTable, byte* mlCodeTable, nuint nbSeq, ZSTD_entropyCTables_t* entropy, ZSTD_entropyCTablesMetadata_t* entropyMetadata, void* workspace, nuint wkspSize, int writeLitEntropy, int writeSeqEntropy) + { + nuint literalsSize = ZSTD_estimateBlockSize_literal(literals, litSize, &entropy->huf, &entropyMetadata->hufMetadata, workspace, wkspSize, writeLitEntropy); + nuint seqSize = ZSTD_estimateBlockSize_sequences(ofCodeTable, llCodeTable, mlCodeTable, nbSeq, &entropy->fse, &entropyMetadata->fseMetadata, workspace, wkspSize, writeSeqEntropy); + + return seqSize + literalsSize + ZSTD_blockHeaderSize; + } + + /* Builds entropy statistics and uses them for blocksize estimation. + * + * Returns the estimated compressed size of the seqStore, or a zstd error. + */ + private static nuint ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(seqStore_t* seqStore, ZSTD_CCtx_s* zc) + { + ZSTD_entropyCTablesMetadata_t entropyMetadata; + + + { + nuint err_code = (ZSTD_buildBlockEntropyStats(seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, &entropyMetadata, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(4) * (uint)((((35) > (52) ? (35) : (52)) + 2)))))); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + return ZSTD_estimateBlockSize(seqStore->litStart, (nuint)(seqStore->lit - seqStore->litStart), seqStore->ofCode, seqStore->llCode, seqStore->mlCode, (nuint)(seqStore->sequences - seqStore->sequencesStart), &zc->blockState.nextCBlock->entropy, &entropyMetadata, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(sizeof(uint)) * (uint)((((35) > (52) ? (35) : (52)) + 2)))), (entropyMetadata.hufMetadata.hType == symbolEncodingType_e.set_compressed ? 1 : 0), 1); + } + + /* Returns literals bytes represented in a seqStore */ + private static nuint ZSTD_countSeqStoreLiteralsBytes(seqStore_t* seqStore) + { + nuint literalsBytes = 0; + nuint nbSeqs = (nuint)(seqStore->sequences - seqStore->sequencesStart); + nuint i; + + for (i = 0; i < nbSeqs; ++i) + { + seqDef_s seq = seqStore->sequencesStart[i]; + + literalsBytes += seq.litLength; + if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_literalLength) + { + literalsBytes += 0x10000; + } + } + + return literalsBytes; + } + + /* Returns match bytes represented in a seqStore */ + private static nuint ZSTD_countSeqStoreMatchBytes(seqStore_t* seqStore) + { + nuint matchBytes = 0; + nuint nbSeqs = (nuint)(seqStore->sequences - seqStore->sequencesStart); + nuint i; + + for (i = 0; i < nbSeqs; ++i) + { + seqDef_s seq = seqStore->sequencesStart[i]; + + matchBytes += (nuint)(seq.matchLength + 3); + if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_matchLength) + { + matchBytes += 0x10000; + } + } + + return matchBytes; + } + + /* Derives the seqStore that is a chunk of the originalSeqStore from [startIdx, endIdx). + * Stores the result in resultSeqStore. + */ + private static void ZSTD_deriveSeqStoreChunk(seqStore_t* resultSeqStore, seqStore_t* originalSeqStore, nuint startIdx, nuint endIdx) + { + byte* litEnd = originalSeqStore->lit; + nuint literalsBytes; + nuint literalsBytesPreceding = 0; + + *resultSeqStore = *originalSeqStore; + if (startIdx > 0) + { + resultSeqStore->sequences = originalSeqStore->sequencesStart + startIdx; + literalsBytesPreceding = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); + } + + if (originalSeqStore->longLengthType != ZSTD_longLengthType_e.ZSTD_llt_none) + { + if (originalSeqStore->longLengthPos < startIdx || originalSeqStore->longLengthPos > endIdx) + { + resultSeqStore->longLengthType = ZSTD_longLengthType_e.ZSTD_llt_none; + } + else + { + resultSeqStore->longLengthPos -= (uint)(startIdx); + } + } + + resultSeqStore->sequencesStart = originalSeqStore->sequencesStart + startIdx; + resultSeqStore->sequences = originalSeqStore->sequencesStart + endIdx; + literalsBytes = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); + resultSeqStore->litStart += literalsBytesPreceding; + if (endIdx == (nuint)(originalSeqStore->sequences - originalSeqStore->sequencesStart)) + { + resultSeqStore->lit = litEnd; + } + else + { + resultSeqStore->lit = resultSeqStore->litStart + literalsBytes; + } + + resultSeqStore->llCode += startIdx; + resultSeqStore->mlCode += startIdx; + resultSeqStore->ofCode += startIdx; + } + + /** + * Returns the raw offset represented by the combination of offCode, ll0, and repcode history. + * offCode must be an offCode representing a repcode, therefore in the range of [0, 2]. + */ + private static uint ZSTD_resolveRepcodeToRawOffset(uint* rep, uint offCode, uint ll0) + { + uint adjustedOffCode = offCode + ll0; + + assert(offCode < 3); + if (adjustedOffCode == 3) + { + assert(rep[0] > 0); + return rep[0] - 1; + } + + return rep[adjustedOffCode]; + } + + /** + * ZSTD_seqStore_resolveOffCodes() reconciles any possible divergences in offset history that may arise + * due to emission of RLE/raw blocks that disturb the offset history, and replaces any repcodes within + * the seqStore that may be invalid. + * + * dRepcodes are updated as would be on the decompression side. cRepcodes are updated exactly in + * accordance with the seqStore. + */ + private static void ZSTD_seqStore_resolveOffCodes(repcodes_s* dRepcodes, repcodes_s* cRepcodes, seqStore_t* seqStore, uint nbSeq) + { + uint idx = 0; + + for (; idx < nbSeq; ++idx) + { + seqDef_s* seq = seqStore->sequencesStart + idx; + uint ll0 = (((seq->litLength == 0)) ? 1U : 0U); + uint offCode = seq->offset - 1; + + assert(seq->offset > 0); + if (offCode <= (uint)((3 - 1))) + { + uint dRawOffset = ZSTD_resolveRepcodeToRawOffset(dRepcodes->rep, offCode, ll0); + uint cRawOffset = ZSTD_resolveRepcodeToRawOffset(cRepcodes->rep, offCode, ll0); + + if (dRawOffset != cRawOffset) + { + seq->offset = cRawOffset + 3; + } + } + + *dRepcodes = ZSTD_updateRep(dRepcodes->rep, seq->offset - 1, ll0); + *cRepcodes = ZSTD_updateRep(cRepcodes->rep, offCode, ll0); + } + } + + /* ZSTD_compressSeqStore_singleBlock(): + * Compresses a seqStore into a block with a block header, into the buffer dst. + * + * Returns the total size of that block (including header) or a ZSTD error code. + */ + private static nuint ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx_s* zc, seqStore_t* seqStore, repcodes_s* dRep, repcodes_s* cRep, void* dst, nuint dstCapacity, void* src, nuint srcSize, uint lastBlock, uint isPartition) + { + uint rleMaxLength = 25; + byte* op = (byte*)(dst); + byte* ip = (byte*)(src); + nuint cSize; + nuint cSeqsSize; + repcodes_s dRepOriginal = *dRep; + + if (isPartition != 0) + { + ZSTD_seqStore_resolveOffCodes(dRep, cRep, seqStore, (uint)(seqStore->sequences - seqStore->sequencesStart)); + } + + cSeqsSize = ZSTD_entropyCompressSeqStore(seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, (void*)(op + ZSTD_blockHeaderSize), dstCapacity - ZSTD_blockHeaderSize, srcSize, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(sizeof(uint)) * (uint)((((35) > (52) ? (35) : (52)) + 2)))), zc->bmi2); + + { + nuint err_code = (cSeqsSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + if (zc->isFirstBlock == 0 && cSeqsSize < rleMaxLength && (ZSTD_isRLE((byte*)(src), srcSize)) != 0) + { + cSeqsSize = 1; + } + + if (zc->seqCollector.collectSequences != 0) + { + ZSTD_copyBlockSequences(zc); + ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); + return 0; + } + + if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat.FSE_repeat_valid) + { + zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat.FSE_repeat_check; + } + + if (cSeqsSize == 0) + { + cSize = ZSTD_noCompressBlock((void*)op, dstCapacity, (void*)ip, srcSize, lastBlock); + + { + nuint err_code = (cSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + *dRep = dRepOriginal; + } + else if (cSeqsSize == 1) + { + cSize = ZSTD_rleCompressBlock((void*)op, dstCapacity, *ip, srcSize, lastBlock); + + { + nuint err_code = (cSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + *dRep = dRepOriginal; + } + else + { + ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); + writeBlockHeader((void*)op, cSeqsSize, srcSize, lastBlock); + cSize = ZSTD_blockHeaderSize + cSeqsSize; + } + + return cSize; + } + + /* Helper function to perform the recursive search for block splits. + * Estimates the cost of seqStore prior to split, and estimates the cost of splitting the sequences in half. + * If advantageous to split, then we recurse down the two sub-blocks. If not, or if an error occurred in estimation, then + * we do not recurse. + * + * Note: The recursion depth is capped by a heuristic minimum number of sequences, defined by MIN_SEQUENCES_BLOCK_SPLITTING. + * In theory, this means the absolute largest recursion depth is 10 == log2(maxNbSeqInBlock/MIN_SEQUENCES_BLOCK_SPLITTING). + * In practice, recursion depth usually doesn't go beyond 4. + * + * Furthermore, the number of splits is capped by MAX_NB_SPLITS. At MAX_NB_SPLITS == 196 with the current existing blockSize + * maximum of 128 KB, this value is actually impossible to reach. + */ + private static void ZSTD_deriveBlockSplitsHelper(seqStoreSplits* splits, nuint startIdx, nuint endIdx, ZSTD_CCtx_s* zc, seqStore_t* origSeqStore) + { + seqStore_t fullSeqStoreChunk; + seqStore_t firstHalfSeqStore; + seqStore_t secondHalfSeqStore; + nuint estimatedOriginalSize; + nuint estimatedFirstHalfSize; + nuint estimatedSecondHalfSize; + nuint midIdx = (startIdx + endIdx) / 2; + + if (endIdx - startIdx < 300 || splits->idx >= 196) + { + return; + } + + ZSTD_deriveSeqStoreChunk(&fullSeqStoreChunk, origSeqStore, startIdx, endIdx); + ZSTD_deriveSeqStoreChunk(&firstHalfSeqStore, origSeqStore, startIdx, midIdx); + ZSTD_deriveSeqStoreChunk(&secondHalfSeqStore, origSeqStore, midIdx, endIdx); + estimatedOriginalSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&fullSeqStoreChunk, zc); + estimatedFirstHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&firstHalfSeqStore, zc); + estimatedSecondHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&secondHalfSeqStore, zc); + if ((ERR_isError(estimatedOriginalSize)) != 0 || (ERR_isError(estimatedFirstHalfSize)) != 0 || (ERR_isError(estimatedSecondHalfSize)) != 0) + { + return; + } + + if (estimatedFirstHalfSize + estimatedSecondHalfSize < estimatedOriginalSize) + { + ZSTD_deriveBlockSplitsHelper(splits, startIdx, midIdx, zc, origSeqStore); + splits->splitLocations[splits->idx] = (uint)(midIdx); + splits->idx++; + ZSTD_deriveBlockSplitsHelper(splits, midIdx, endIdx, zc, origSeqStore); + } + } + + /* Base recursive function. Populates a table with intra-block partition indices that can improve compression ratio. + * + * Returns the number of splits made (which equals the size of the partition table - 1). */ - public static nuint ZSTD_generateSequences(ZSTD_CCtx_s* zc, ZSTD_Sequence* outSeqs, nuint outSeqsSize, void* src, nuint srcSize) + private static nuint ZSTD_deriveBlockSplits(ZSTD_CCtx_s* zc, uint* partitions, uint nbSeq) { - nuint dstCapacity = ZSTD_compressBound(srcSize); - void* dst = ZSTD_customMalloc(dstCapacity, ZSTD_defaultCMem); - SeqCollector seqCollector; + seqStoreSplits splits = new seqStoreSplits + { + splitLocations = partitions, + idx = 0, + }; - if (dst == null) + if (nbSeq <= 4) { - return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_memory_allocation))); + return 0; } - seqCollector.collectSequences = 1; - seqCollector.seqStart = outSeqs; - seqCollector.seqIndex = 0; - seqCollector.maxSequences = outSeqsSize; - zc->seqCollector = seqCollector; - ZSTD_compress2(zc, dst, dstCapacity, src, srcSize); - ZSTD_customFree(dst, ZSTD_defaultCMem); - return zc->seqCollector.seqIndex; + ZSTD_deriveBlockSplitsHelper(&splits, 0, nbSeq, zc, &zc->seqStore); + splits.splitLocations[splits.idx] = nbSeq; + return splits.idx; } - /*! ZSTD_mergeBlockDelimiters() : - * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals - * by merging them into into the literals of the next sequence. - * - * As such, the final generated result has no explicit representation of block boundaries, - * and the final last literals segment is not represented in the sequences. + /* ZSTD_compressBlock_splitBlock(): + * Attempts to split a given block into multiple blocks to improve compression ratio. * - * The output of this function can be fed into ZSTD_compressSequences() with CCtx - * setting of ZSTD_c_blockDelimiters as ZSTD_sf_noBlockDelimiters - * @return : number of sequences left after merging + * Returns combined size of all blocks (which includes headers), or a ZSTD error code. */ - public static nuint ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, nuint seqsSize) + private static nuint ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx_s* zc, void* dst, nuint dstCapacity, void* src, nuint blockSize, uint lastBlock, uint nbSeq) { - nuint @in = 0; - nuint @out = 0; + nuint cSize = 0; + byte* ip = (byte*)(src); + byte* op = (byte*)(dst); + uint* partitions = stackalloc uint[196]; + nuint i = 0; + nuint srcBytesTotal = 0; + nuint numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq); + seqStore_t nextSeqStore; + var _ = &nextSeqStore; + seqStore_t currSeqStore; + repcodes_s dRep; + repcodes_s cRep; - for (; @in < seqsSize; ++@in) + memcpy((void*)(dRep.rep), (void*)(zc->blockState.prevCBlock->rep), ((nuint)(sizeof(repcodes_s)))); + memcpy((void*)(cRep.rep), (void*)(zc->blockState.prevCBlock->rep), ((nuint)(sizeof(repcodes_s)))); + if (numSplits == 0) { - if (sequences[@in].offset == 0 && sequences[@in].matchLength == 0) + nuint cSizeSingleBlock = ZSTD_compressSeqStore_singleBlock(zc, &zc->seqStore, &dRep, &cRep, (void*)op, dstCapacity, (void*)ip, blockSize, lastBlock, 0); + + { - if (@in != seqsSize - 1) + nuint err_code = (cSizeSingleBlock); + + if ((ERR_isError(err_code)) != 0) { - sequences[@in + 1].litLength += sequences[@in].litLength; + return err_code; } } + + assert(cSizeSingleBlock <= (uint)((1 << 17)) + ZSTD_blockHeaderSize); + return cSizeSingleBlock; + } + + ZSTD_deriveSeqStoreChunk(&currSeqStore, &zc->seqStore, 0, partitions[0]); + for (i = 0; i <= numSplits; ++i) + { + nuint srcBytes; + nuint cSizeChunk; + uint lastPartition = (((i == numSplits)) ? 1U : 0U); + uint lastBlockEntireSrc = 0; + + srcBytes = ZSTD_countSeqStoreLiteralsBytes(&currSeqStore) + ZSTD_countSeqStoreMatchBytes(&currSeqStore); + srcBytesTotal += srcBytes; + if (lastPartition != 0) + { + srcBytes += blockSize - srcBytesTotal; + lastBlockEntireSrc = lastBlock; + } else { - sequences[@out] = sequences[@in]; - ++@out; + ZSTD_deriveSeqStoreChunk(&nextSeqStore, &zc->seqStore, partitions[i], partitions[i + 1]); + } + + cSizeChunk = ZSTD_compressSeqStore_singleBlock(zc, &currSeqStore, &dRep, &cRep, (void*)op, dstCapacity, (void*)ip, srcBytes, lastBlockEntireSrc, 1); + + { + nuint err_code = (cSizeChunk); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } } + + ip += srcBytes; + op += cSizeChunk; + dstCapacity -= cSizeChunk; + cSize += cSizeChunk; + currSeqStore = nextSeqStore; + assert(cSizeChunk <= (uint)((1 << 17)) + ZSTD_blockHeaderSize); } - return @out; + memcpy((void*)(zc->blockState.prevCBlock->rep), (void*)(dRep.rep), ((nuint)(sizeof(repcodes_s)))); + return cSize; } - /* Unrolled loop to read four size_ts of input at a time. Returns 1 if is RLE, 0 if not. */ - private static int ZSTD_isRLE(byte* src, nuint length) + private static nuint ZSTD_compressBlock_splitBlock(ZSTD_CCtx_s* zc, void* dst, nuint dstCapacity, void* src, nuint srcSize, uint lastBlock) { - byte* ip = src; - byte value = ip[0]; - nuint valueST = (nuint)((ulong)(value) * 0x0101010101010101UL); - nuint unrollSize = (nuint)(sizeof(nuint)) * 4; - nuint unrollMask = unrollSize - 1; - nuint prefixLength = length & unrollMask; - nuint i; - nuint u; + byte* ip = (byte*)(src); + byte* op = (byte*)(dst); + uint nbSeq; + nuint cSize; - if (length == 1) - { - return 1; - } - if (prefixLength != 0 && ZSTD_count(ip + 1, ip, ip + prefixLength) != prefixLength - 1) { - return 0; - } + nuint bss = ZSTD_buildSeqStore(zc, src, srcSize); + - for (i = prefixLength; i != length; i += unrollSize) - { - for (u = 0; u < unrollSize; u += (nuint)(sizeof(nuint))) { - if (MEM_readST((void*)(ip + i + u)) != valueST) + nuint err_code = (bss); + + if ((ERR_isError(err_code)) != 0) { - return 0; + return err_code; } } - } - return 1; - } + if (bss == (nuint)ZSTD_buildSeqStore_e.ZSTDbss_noCompress) + { + if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat.FSE_repeat_valid) + { + zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat.FSE_repeat_check; + } - /* Returns true if the given block may be RLE. - * This is just a heuristic based on the compressibility. - * It may return both false positives and false negatives. - */ - private static int ZSTD_maybeRLE(seqStore_t* seqStore) - { - nuint nbSeqs = (nuint)(seqStore->sequences - seqStore->sequencesStart); - nuint nbLits = (nuint)(seqStore->lit - seqStore->litStart); + cSize = ZSTD_noCompressBlock((void*)op, dstCapacity, (void*)ip, srcSize, lastBlock); - return ((nbSeqs < 4 && nbLits < 10) ? 1 : 0); - } + { + nuint err_code = (cSize); - private static void ZSTD_confirmRepcodesAndEntropyTables(ZSTD_CCtx_s* zc) - { - ZSTD_compressedBlockState_t* tmp = zc->blockState.prevCBlock; + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } - zc->blockState.prevCBlock = zc->blockState.nextCBlock; - zc->blockState.nextCBlock = tmp; + return cSize; + } + + nbSeq = (uint)(zc->seqStore.sequences - zc->seqStore.sequencesStart); + } + + assert(zc->appliedParams.splitBlocks == 1); + cSize = ZSTD_compressBlock_splitBlock_internal(zc, dst, dstCapacity, src, srcSize, lastBlock, nbSeq); + + { + nuint err_code = (cSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + return cSize; } private static nuint ZSTD_compressBlock_internal(ZSTD_CCtx_s* zc, void* dst, nuint dstCapacity, void* src, nuint srcSize, uint frame) @@ -3435,11 +4534,11 @@ private static nuint ZSTD_compressBlock_internal(ZSTD_CCtx_s* zc, void* dst, nui if (zc->seqCollector.collectSequences != 0) { ZSTD_copyBlockSequences(zc); - ZSTD_confirmRepcodesAndEntropyTables(zc); + ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); return 0; } - cSize = ZSTD_entropyCompressSequences(&zc->seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, dst, dstCapacity, srcSize, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(sizeof(uint)) * (uint)((((35) > (52) ? (35) : (52)) + 2)))), zc->bmi2); + cSize = ZSTD_entropyCompressSeqStore(&zc->seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, dst, dstCapacity, srcSize, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(sizeof(uint)) * (uint)((((35) > (52) ? (35) : (52)) + 2)))), zc->bmi2); if (zc->seqCollector.collectSequences != 0) { ZSTD_copyBlockSequences(zc); @@ -3455,7 +4554,7 @@ private static nuint ZSTD_compressBlock_internal(ZSTD_CCtx_s* zc, void* dst, nui @out: if ((ERR_isError(cSize)) == 0 && cSize > 1) { - ZSTD_confirmRepcodesAndEntropyTables(zc); + ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); } if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat.FSE_repeat_valid) @@ -3495,7 +4594,7 @@ private static nuint ZSTD_compressBlock_targetCBlockSize_body(ZSTD_CCtx_s* zc, v if (cSize != 0 && cSize < maxCSize + ZSTD_blockHeaderSize) { - ZSTD_confirmRepcodesAndEntropyTables(zc); + ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); return cSize; } } @@ -3541,10 +4640,11 @@ private static nuint ZSTD_compressBlock_targetCBlockSize(ZSTD_CCtx_s* zc, void* private static void ZSTD_overflowCorrectIfNeeded(ZSTD_matchState_t* ms, ZSTD_cwksp* ws, ZSTD_CCtx_params_s* @params, void* ip, void* iend) { - if ((ZSTD_window_needOverflowCorrection(ms->window, iend)) != 0) + uint cycleLog = ZSTD_cycleLog(@params->cParams.chainLog, @params->cParams.strategy); + uint maxDist = (uint)(1) << (int)@params->cParams.windowLog; + + if ((ZSTD_window_needOverflowCorrection(ms->window, cycleLog, maxDist, ms->loadedDictEnd, ip, iend)) != 0) { - uint maxDist = (uint)(1) << (int)@params->cParams.windowLog; - uint cycleLog = ZSTD_cycleLog(@params->cParams.chainLog, @params->cParams.strategy); uint correction = ZSTD_window_correctOverflow(&ms->window, cycleLog, maxDist, ip); ZSTD_cwksp_mark_tables_dirty(ws); @@ -3628,6 +4728,21 @@ private static nuint ZSTD_compress_frameChunk(ZSTD_CCtx_s* cctx, void* dst, nuin assert(cSize > 0); assert(cSize <= blockSize + ZSTD_blockHeaderSize); } + else if ((ZSTD_blockSplitterEnabled(&cctx->appliedParams)) != 0) + { + cSize = ZSTD_compressBlock_splitBlock(cctx, (void*)op, dstCapacity, (void*)ip, blockSize, lastBlock); + + { + nuint err_code = (cSize); + + if ((ERR_isError(err_code)) != 0) + { + return err_code; + } + } + + assert(cSize > 0 || cctx->seqCollector.collectSequences == 1); + } else { cSize = ZSTD_compressBlock_internal(cctx, (void*)(op + ZSTD_blockHeaderSize), dstCapacity - ZSTD_blockHeaderSize, (void*)ip, blockSize, 1); @@ -3910,14 +5025,15 @@ private static nuint ZSTD_compressContinue_internal(ZSTD_CCtx_s* cctx, void* dst return fhSize; } - if ((ZSTD_window_update(&ms->window, src, srcSize)) == 0) + if ((ZSTD_window_update(&ms->window, src, srcSize, (int)ms->forceNonContiguous)) == 0) { + ms->forceNonContiguous = 0; ms->nextToUpdate = ms->window.dictLimit; } if (cctx->appliedParams.ldmParams.enableLdm != 0) { - ZSTD_window_update(&cctx->ldmState.window, src, srcSize); + ZSTD_window_update(&cctx->ldmState.window, src, srcSize, 0); } if (frame == 0) @@ -3992,83 +5108,105 @@ private static nuint ZSTD_loadDictionaryContent(ZSTD_matchState_t* ms, ldmState_ { byte* ip = (byte*)(src); byte* iend = ip + srcSize; + int loadLdmDict = ((@params->ldmParams.enableLdm != 0 && ls != null) ? 1 : 0); + + ZSTD_assertEqualCParams(@params->cParams, ms->cParams); + if (srcSize > ((unchecked((uint)(-1))) - ((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31)))))) + { + uint maxDictSize = ((3U << 29) + (1U << (unchecked((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31))))) - 1; + + assert((ZSTD_window_isEmpty(ms->window)) != 0); + if (loadLdmDict != 0) + { + assert((ZSTD_window_isEmpty(ls->window)) != 0); + } + + if (srcSize > maxDictSize) + { + ip = iend - maxDictSize; + src = ip; + srcSize = maxDictSize; + } + } - ZSTD_window_update(&ms->window, src, srcSize); + ZSTD_window_update(&ms->window, src, srcSize, 0); ms->loadedDictEnd = (uint)(@params->forceWindow != 0 ? 0 : (uint)(iend - ms->window.@base)); - if (@params->ldmParams.enableLdm != 0 && ls != null) + ms->forceNonContiguous = (uint)@params->deterministicRefPrefix; + if (loadLdmDict != 0) { - ZSTD_window_update(&ls->window, src, srcSize); + ZSTD_window_update(&ls->window, src, srcSize, 0); ls->loadedDictEnd = (uint)(@params->forceWindow != 0 ? 0 : (uint)(iend - ls->window.@base)); } - ZSTD_assertEqualCParams(@params->cParams, ms->cParams); if (srcSize <= 8) { return 0; } - while (iend - ip > 8) + ZSTD_overflowCorrectIfNeeded(ms, ws, @params, (void*)ip, (void*)iend); + if (loadLdmDict != 0) { - nuint remaining = (nuint)(iend - ip); - nuint chunk = ((remaining) < (((unchecked((uint)(-1))) - ((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31)))))) ? (remaining) : (((unchecked((uint)(-1))) - ((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31))))))); - byte* ichunk = ip + chunk; + ZSTD_ldm_fillHashTable(ls, ip, iend, &@params->ldmParams); + } - ZSTD_overflowCorrectIfNeeded(ms, ws, @params, (void*)ip, (void*)ichunk); - if (@params->ldmParams.enableLdm != 0 && ls != null) + switch (@params->cParams.strategy) + { + case ZSTD_strategy.ZSTD_fast: { - ZSTD_ldm_fillHashTable(ls, (byte*)(src), (byte*)(src) + srcSize, &@params->ldmParams); + ZSTD_fillHashTable(ms, (void*)iend, dtlm); } - switch (@params->cParams.strategy) + break; + case ZSTD_strategy.ZSTD_dfast: { - case ZSTD_strategy.ZSTD_fast: - { - ZSTD_fillHashTable(ms, (void*)ichunk, dtlm); - } + ZSTD_fillDoubleHashTable(ms, (void*)iend, dtlm); + } - break; - case ZSTD_strategy.ZSTD_dfast: - { - ZSTD_fillDoubleHashTable(ms, (void*)ichunk, dtlm); - } + break; + case ZSTD_strategy.ZSTD_greedy: + case ZSTD_strategy.ZSTD_lazy: + case ZSTD_strategy.ZSTD_lazy2: + { + assert(srcSize >= 8); + } - break; - case ZSTD_strategy.ZSTD_greedy: - case ZSTD_strategy.ZSTD_lazy: - case ZSTD_strategy.ZSTD_lazy2: + if (ms->dedicatedDictSearch != 0) + { + assert(ms->chainTable != null); + ZSTD_dedicatedDictSearch_lazy_loadDictionary(ms, iend - 8); + } + else + { + assert(@params->useRowMatchFinder != ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto); + if (@params->useRowMatchFinder == ZSTD_useRowMatchFinderMode_e.ZSTD_urm_enableRowMatchFinder) { - if (chunk >= 8 && ms->dedicatedDictSearch != 0) - { - assert(chunk == remaining); - ZSTD_dedicatedDictSearch_lazy_loadDictionary(ms, ichunk - 8); - } - else if (chunk >= 8) - { - ZSTD_insertAndFindFirstIndex(ms, ichunk - 8); - } - } + nuint tagTableSize = ((nuint)(1) << (int)@params->cParams.hashLog) * (nuint)(2); - break; - case ZSTD_strategy.ZSTD_btlazy2: - case ZSTD_strategy.ZSTD_btopt: - case ZSTD_strategy.ZSTD_btultra: - case ZSTD_strategy.ZSTD_btultra2: - { - if (chunk >= 8) - { - ZSTD_updateTree(ms, ichunk - 8, ichunk); - } + memset((void*)(ms->tagTable), (0), (tagTableSize)); + ZSTD_row_update(ms, iend - 8); } - - break; - default: + else { - assert(0 != 0); + ZSTD_insertAndFindFirstIndex(ms, iend - 8); } - break; } - ip = ichunk; + break; + case ZSTD_strategy.ZSTD_btlazy2: + case ZSTD_strategy.ZSTD_btopt: + case ZSTD_strategy.ZSTD_btultra: + case ZSTD_strategy.ZSTD_btultra2: + { + assert(srcSize >= 8); + } + + ZSTD_updateTree(ms, iend - 8, iend); + break; + default: + { + assert(0 != 0); + } + break; } ms->nextToUpdate = (uint)(iend - ms->window.@base); @@ -4347,6 +5485,8 @@ private static nuint ZSTD_compress_insertDictionary(ZSTD_compressedBlockState_t* * @return : 0, or an error code */ private static nuint ZSTD_compressBegin_internal(ZSTD_CCtx_s* cctx, void* dict, nuint dictSize, ZSTD_dictContentType_e dictContentType, ZSTD_dictTableLoadMethod_e dtlm, ZSTD_CDict_s* cdict, ZSTD_CCtx_params_s* @params, ulong pledgedSrcSize, ZSTD_buffered_policy_e zbuff) { + nuint dictContentSize = cdict != null ? cdict->dictContentSize : dictSize; + assert((ERR_isError(ZSTD_checkCParams(@params->cParams))) == 0); assert(!((dict) != null && (cdict) != null)); if ((cdict) != null && (cdict->dictContentSize > 0) && (pledgedSrcSize < (uint)((128 * (1 << 10))) || pledgedSrcSize < cdict->dictContentSize * (6UL) || pledgedSrcSize == (unchecked(0UL - 1)) || cdict->compressionLevel == 0) && (@params->attachDictPref != ZSTD_dictAttachPref_e.ZSTD_dictForceLoad)) @@ -4356,7 +5496,7 @@ private static nuint ZSTD_compressBegin_internal(ZSTD_CCtx_s* cctx, void* dict, { - nuint err_code = (ZSTD_resetCCtx_internal(cctx, *@params, pledgedSrcSize, ZSTD_compResetPolicy_e.ZSTDcrp_makeClean, zbuff)); + nuint err_code = (ZSTD_resetCCtx_internal(cctx, @params, pledgedSrcSize, dictContentSize, ZSTD_compResetPolicy_e.ZSTDcrp_makeClean, zbuff)); if ((ERR_isError(err_code)) != 0) { @@ -4380,7 +5520,7 @@ private static nuint ZSTD_compressBegin_internal(ZSTD_CCtx_s* cctx, void* dict, assert(dictID <= 0xffffffff); cctx->dictID = (uint)(dictID); - cctx->dictContentSize = cdict != null ? cdict->dictContentSize : dictSize; + cctx->dictContentSize = dictContentSize; } return 0; @@ -4546,11 +5686,9 @@ public static nuint ZSTD_compressEnd(ZSTD_CCtx_s* cctx, void* dst, nuint dstCapa /*! ZSTD_compress_advanced() : * Note : this function is now DEPRECATED. * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_setParameter() and other parameter setters. - * This prototype will be marked as deprecated and generate compilation warning on reaching v1.5.x */ + * This prototype will generate compilation warnings. */ public static nuint ZSTD_compress_advanced(ZSTD_CCtx_s* cctx, void* dst, nuint dstCapacity, void* src, nuint srcSize, void* dict, nuint dictSize, ZSTD_parameters @params) { - ZSTD_CCtx_params_s cctxParams; - { nuint err_code = (ZSTD_checkCParams(@params.cParams)); @@ -4561,8 +5699,8 @@ public static nuint ZSTD_compress_advanced(ZSTD_CCtx_s* cctx, void* dst, nuint d } } - ZSTD_CCtxParams_init_internal(&cctxParams, &@params, 0); - return ZSTD_compress_advanced_internal(cctx, dst, dstCapacity, src, srcSize, dict, dictSize, &cctxParams); + ZSTD_CCtxParams_init_internal(&cctx->simpleApiParams, &@params, 0); + return ZSTD_compress_advanced_internal(cctx, dst, dstCapacity, src, srcSize, dict, dictSize, &cctx->simpleApiParams); } /* Internal */ @@ -4587,23 +5725,21 @@ public static nuint ZSTD_compress_advanced_internal(ZSTD_CCtx_s* cctx, void* dst /*! ZSTD_compress_usingDict() : * Compression at an explicit compression level using a Dictionary. * A dictionary can be any arbitrary data segment (also called a prefix), - * or a buffer with specified information (see dictBuilder/zdict.h). + * or a buffer with specified information (see zdict.h). * Note : This function loads the dictionary, resulting in significant startup delay. * It's intended for a dictionary used only once. * Note 2 : When `dict == NULL || dictSize < 8` no dictionary is used. */ public static nuint ZSTD_compress_usingDict(ZSTD_CCtx_s* cctx, void* dst, nuint dstCapacity, void* src, nuint srcSize, void* dict, nuint dictSize, int compressionLevel) { - ZSTD_CCtx_params_s cctxParams; - { ZSTD_parameters @params = ZSTD_getParams_internal(compressionLevel, (ulong)srcSize, dict != null ? dictSize : 0, ZSTD_cParamMode_e.ZSTD_cpm_noAttachDict); assert(@params.fParams.contentSizeFlag == 1); - ZSTD_CCtxParams_init_internal(&cctxParams, &@params, (compressionLevel == 0) ? 3 : compressionLevel); + ZSTD_CCtxParams_init_internal(&cctx->simpleApiParams, &@params, (compressionLevel == 0) ? 3 : compressionLevel); } - return ZSTD_compress_advanced_internal(cctx, dst, dstCapacity, src, srcSize, dict, dictSize, &cctxParams); + return ZSTD_compress_advanced_internal(cctx, dst, dstCapacity, src, srcSize, dict, dictSize, &cctx->simpleApiParams); } /*! ZSTD_compressCCtx() : @@ -4643,7 +5779,7 @@ public static nuint ZSTD_compress(void* dst, nuint dstCapacity, void* src, nuint * Estimate amount of memory that will be needed to create a dictionary with following arguments */ public static nuint ZSTD_estimateCDictSize_advanced(nuint dictSize, ZSTD_compressionParameters cParams, ZSTD_dictLoadMethod_e dictLoadMethod) { - return ZSTD_cwksp_alloc_size((nuint)(sizeof(ZSTD_CDict_s))) + ZSTD_cwksp_alloc_size((nuint)(((6 << 10) + 256))) + ZSTD_sizeof_matchState(&cParams, 0) + (dictLoadMethod == ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef ? 0 : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, (nuint)(sizeof(void*))))); + return ZSTD_cwksp_alloc_size((nuint)(sizeof(ZSTD_CDict_s))) + ZSTD_cwksp_alloc_size((nuint)(((6 << 10) + 256))) + ZSTD_sizeof_matchState(&cParams, ZSTD_resolveRowMatchFinderMode(ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto, &cParams), 1, 0) + (dictLoadMethod == ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef ? 0 : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, (nuint)(sizeof(void*))))); } /*! ZSTD_estimate?DictSize() : @@ -4673,11 +5809,6 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff assert((ZSTD_checkCParams(@params.cParams)) == 0); cdict->matchState.cParams = @params.cParams; cdict->matchState.dedicatedDictSearch = @params.enableDedicatedDictSearch; - if (cdict->matchState.dedicatedDictSearch != 0 && dictSize > ((unchecked((uint)(-1))) - ((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31)))))) - { - cdict->matchState.dedicatedDictSearch = 0; - } - if ((dictLoadMethod == ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef) || dictBuffer == null || dictSize == 0) { cdict->dictContent = dictBuffer; @@ -4701,7 +5832,7 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff ZSTD_reset_compressedBlockState(&cdict->cBlockState); { - nuint err_code = (ZSTD_reset_matchState(&cdict->matchState, &cdict->workspace, &@params.cParams, ZSTD_compResetPolicy_e.ZSTDcrp_makeClean, ZSTD_indexResetPolicy_e.ZSTDirp_reset, ZSTD_resetTarget_e.ZSTD_resetTarget_CDict)); + nuint err_code = (ZSTD_reset_matchState(&cdict->matchState, &cdict->workspace, &@params.cParams, @params.useRowMatchFinder, ZSTD_compResetPolicy_e.ZSTDcrp_makeClean, ZSTD_indexResetPolicy_e.ZSTDirp_reset, ZSTD_resetTarget_e.ZSTD_resetTarget_CDict)); if ((ERR_isError(err_code)) != 0) { @@ -4735,7 +5866,7 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff return 0; } - private static ZSTD_CDict_s* ZSTD_createCDict_advanced_internal(nuint dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_compressionParameters cParams, ZSTD_customMem customMem) + private static ZSTD_CDict_s* ZSTD_createCDict_advanced_internal(nuint dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_compressionParameters cParams, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, uint enableDedicatedDictSearch, ZSTD_customMem customMem) { if (((customMem.customAlloc == null ? 1 : 0) ^ (customMem.customFree == null ? 1 : 0)) != 0) { @@ -4744,7 +5875,7 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff { - nuint workspaceSize = ZSTD_cwksp_alloc_size((nuint)(sizeof(ZSTD_CDict_s))) + ZSTD_cwksp_alloc_size((nuint)(((6 << 10) + 256))) + ZSTD_sizeof_matchState(&cParams, 0) + (dictLoadMethod == ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef ? 0 : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, (nuint)(sizeof(void*))))); + nuint workspaceSize = ZSTD_cwksp_alloc_size((nuint)(sizeof(ZSTD_CDict_s))) + ZSTD_cwksp_alloc_size((nuint)(((6 << 10) + 256))) + ZSTD_sizeof_matchState(&cParams, useRowMatchFinder, enableDedicatedDictSearch, 0) + (dictLoadMethod == ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef ? 0 : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, (nuint)(sizeof(void*))))); void* workspace = ZSTD_customMalloc(workspaceSize, customMem); ZSTD_cwksp ws; ZSTD_CDict_s* cdict; @@ -4761,6 +5892,7 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff ZSTD_cwksp_move(&cdict->workspace, &ws); cdict->customMem = customMem; cdict->compressionLevel = 0; + cdict->useRowMatchFinder = useRowMatchFinder; return cdict; } } @@ -4807,7 +5939,8 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff } cctxParams.cParams = cParams; - cdict = ZSTD_createCDict_advanced_internal(dictSize, dictLoadMethod, cctxParams.cParams, customMem); + cctxParams.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams.useRowMatchFinder, &cParams); + cdict = ZSTD_createCDict_advanced_internal(dictSize, dictLoadMethod, cctxParams.cParams, cctxParams.useRowMatchFinder, (uint)cctxParams.enableDedicatedDictSearch, customMem); if ((ERR_isError(ZSTD_initCDict_internal(cdict, dict, dictSize, dictLoadMethod, dictContentType, cctxParams))) != 0) { ZSTD_freeCDict(cdict); @@ -4862,7 +5995,8 @@ private static nuint ZSTD_initCDict_internal(ZSTD_CDict_s* cdict, void* dictBuff } /*! ZSTD_freeCDict() : - * Function frees memory allocated by ZSTD_createCDict(). */ + * Function frees memory allocated by ZSTD_createCDict(). + * If a NULL pointer is passed, no operation is performed. */ public static nuint ZSTD_freeCDict(ZSTD_CDict_s* cdict) { if (cdict == null) @@ -4900,7 +6034,8 @@ public static nuint ZSTD_freeCDict(ZSTD_CDict_s* cdict) */ public static ZSTD_CDict_s* ZSTD_initStaticCDict(void* workspace, nuint workspaceSize, void* dict, nuint dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, ZSTD_compressionParameters cParams) { - nuint matchStateSize = ZSTD_sizeof_matchState(&cParams, 0); + ZSTD_useRowMatchFinderMode_e useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(ZSTD_useRowMatchFinderMode_e.ZSTD_urm_auto, &cParams); + nuint matchStateSize = ZSTD_sizeof_matchState(&cParams, useRowMatchFinder, 1, 0); nuint neededSize = ZSTD_cwksp_alloc_size((nuint)(sizeof(ZSTD_CDict_s))) + (dictLoadMethod == ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef ? 0 : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, (nuint)(sizeof(void*))))) + ZSTD_cwksp_alloc_size((nuint)(((6 << 10) + 256))) + matchStateSize; ZSTD_CDict_s* cdict; ZSTD_CCtx_params_s @params; @@ -4931,6 +6066,8 @@ public static nuint ZSTD_freeCDict(ZSTD_CDict_s* cdict) ZSTD_CCtxParams_init(&@params, 0); @params.cParams = cParams; + @params.useRowMatchFinder = useRowMatchFinder; + cdict->useRowMatchFinder = useRowMatchFinder; if ((ERR_isError(ZSTD_initCDict_internal(cdict, dict, dictSize, dictLoadMethod, dictContentType, @params))) != 0) { return (ZSTD_CDict_s*)null; @@ -4961,9 +6098,10 @@ public static uint ZSTD_getDictID_fromCDict(ZSTD_CDict_s* cdict) return cdict->dictID; } - /* ZSTD_compressBegin_usingCDict_advanced() : - * cdict must be != NULL */ - public static nuint ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx_s* cctx, ZSTD_CDict_s* cdict, ZSTD_frameParameters fParams, ulong pledgedSrcSize) + /* ZSTD_compressBegin_usingCDict_internal() : + * Implementation of various ZSTD_compressBegin_usingCDict* functions. + */ + private static nuint ZSTD_compressBegin_usingCDict_internal(ZSTD_CCtx_s* cctx, ZSTD_CDict_s* cdict, ZSTD_frameParameters fParams, ulong pledgedSrcSize) { ZSTD_CCtx_params_s cctxParams; @@ -4992,9 +6130,16 @@ public static nuint ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx_s* cctx, ZS return ZSTD_compressBegin_internal(cctx, null, 0, ZSTD_dictContentType_e.ZSTD_dct_auto, ZSTD_dictTableLoadMethod_e.ZSTD_dtlm_fast, cdict, &cctxParams, pledgedSrcSize, ZSTD_buffered_policy_e.ZSTDb_not_buffered); } + /* ZSTD_compressBegin_usingCDict_advanced() : + * This function is DEPRECATED. + * cdict must be != NULL */ + public static nuint ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx_s* cctx, ZSTD_CDict_s* cdict, ZSTD_frameParameters fParams, ulong pledgedSrcSize) + { + return ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, pledgedSrcSize); + } + /* ZSTD_compressBegin_usingCDict() : - * pledgedSrcSize=0 means "unknown" - * if pledgedSrcSize>0, it will enable contentSizeFlag */ + * cdict must be != NULL */ public static nuint ZSTD_compressBegin_usingCDict(ZSTD_CCtx_s* cctx, ZSTD_CDict_s* cdict) { ZSTD_frameParameters fParams = new ZSTD_frameParameters @@ -5004,18 +6149,17 @@ public static nuint ZSTD_compressBegin_usingCDict(ZSTD_CCtx_s* cctx, ZSTD_CDict_ noDictIDFlag = 0, }; - return ZSTD_compressBegin_usingCDict_advanced(cctx, cdict, fParams, (unchecked(0UL - 1))); + return ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, (unchecked(0UL - 1))); } - /*! ZSTD_compress_usingCDict_advanced() : - * Note : this function is now REDUNDANT. - * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_loadDictionary() and other parameter setters. - * This prototype will be marked as deprecated and generate compilation warning in some future version */ - public static nuint ZSTD_compress_usingCDict_advanced(ZSTD_CCtx_s* cctx, void* dst, nuint dstCapacity, void* src, nuint srcSize, ZSTD_CDict_s* cdict, ZSTD_frameParameters fParams) + /*! ZSTD_compress_usingCDict_internal(): + * Implementation of various ZSTD_compress_usingCDict* functions. + */ + private static nuint ZSTD_compress_usingCDict_internal(ZSTD_CCtx_s* cctx, void* dst, nuint dstCapacity, void* src, nuint srcSize, ZSTD_CDict_s* cdict, ZSTD_frameParameters fParams) { { - nuint err_code = (ZSTD_compressBegin_usingCDict_advanced(cctx, cdict, fParams, (ulong)srcSize)); + nuint err_code = (ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, (ulong)srcSize)); if ((ERR_isError(err_code)) != 0) { @@ -5026,6 +6170,14 @@ public static nuint ZSTD_compress_usingCDict_advanced(ZSTD_CCtx_s* cctx, void* d return ZSTD_compressEnd(cctx, dst, dstCapacity, src, srcSize); } + /*! ZSTD_compress_usingCDict_advanced(): + * This function is DEPRECATED. + */ + public static nuint ZSTD_compress_usingCDict_advanced(ZSTD_CCtx_s* cctx, void* dst, nuint dstCapacity, void* src, nuint srcSize, ZSTD_CDict_s* cdict, ZSTD_frameParameters fParams) + { + return ZSTD_compress_usingCDict_internal(cctx, dst, dstCapacity, src, srcSize, cdict, fParams); + } + /*! ZSTD_compress_usingCDict() : * Compression using a digested Dictionary. * Faster startup than ZSTD_compress_usingDict(), recommended when same dictionary is used multiple times. @@ -5040,7 +6192,7 @@ public static nuint ZSTD_compress_usingCDict(ZSTD_CCtx_s* cctx, void* dst, nuint noDictIDFlag = 0, }; - return ZSTD_compress_usingCDict_advanced(cctx, dst, dstCapacity, src, srcSize, cdict, fParams); + return ZSTD_compress_usingCDict_internal(cctx, dst, dstCapacity, src, srcSize, cdict, fParams); } /* ****************************************************************** @@ -5294,7 +6446,7 @@ public static nuint ZSTD_initCStream_advanced(ZSTD_CCtx_s* zcs, void* dict, nuin } /*! ZSTD_initCStream_usingDict() : - * This function is deprecated, and is equivalent to: + * This function is DEPRECATED, and is equivalent to: * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize); @@ -5303,7 +6455,7 @@ public static nuint ZSTD_initCStream_advanced(ZSTD_CCtx_s* zcs, void* dict, nuin * dict == NULL or dictSize < 8, in which case no dict is used. * Note: dict is loaded with ZSTD_dct_auto (treated as a full zstd dictionary if * it begins with ZSTD_MAGIC_DICTIONARY, else as raw content) and ZSTD_dlm_byCopy. - * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + * This prototype will generate compilation warnings. */ public static nuint ZSTD_initCStream_usingDict(ZSTD_CCtx_s* zcs, void* dict, nuint dictSize, int compressionLevel) { @@ -5341,7 +6493,7 @@ public static nuint ZSTD_initCStream_usingDict(ZSTD_CCtx_s* zcs, void* dict, nui } /*! ZSTD_initCStream_srcSize() : - * This function is deprecated, and equivalent to: + * This function is DEPRECATED, and equivalent to: * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any) * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); @@ -5350,7 +6502,7 @@ public static nuint ZSTD_initCStream_usingDict(ZSTD_CCtx_s* zcs, void* dict, nui * pledgedSrcSize must be correct. If it is not known at init time, use * ZSTD_CONTENTSIZE_UNKNOWN. Note that, for compatibility with older programs, * "0" also disables frame content size field. It may be enabled in the future. - * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + * This prototype will generate compilation warnings. */ public static nuint ZSTD_initCStream_srcSize(ZSTD_CCtx_s* zcs, int compressionLevel, ulong pss) { @@ -5791,7 +6943,7 @@ private static nuint ZSTD_CCtx_init_compressStream2(ZSTD_CCtx_s* cctx, ZSTD_EndD memset((void*)(&cctx->prefixDict), (0), ((nuint)(sizeof(ZSTD_prefixDict_s)))); assert(prefixDict.dict == null || cctx->cdict == null); - if (cctx->cdict != null) + if (cctx->cdict != null && cctx->localDict.cdict == null) { @params.compressionLevel = cctx->cdict->compressionLevel; } @@ -5814,6 +6966,12 @@ private static nuint ZSTD_CCtx_init_compressStream2(ZSTD_CCtx_s* cctx, ZSTD_EndD @params.ldmParams.enableLdm = 1; } + if ((ZSTD_CParams_useBlockSplitter(&@params.cParams)) != 0) + { + @params.splitBlocks = 1; + } + + @params.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(@params.useRowMatchFinder, &@params.cParams); { ulong pledgedSrcSize = cctx->pledgedSrcSizePlusOne - 1; @@ -5849,7 +7007,7 @@ private static nuint ZSTD_CCtx_init_compressStream2(ZSTD_CCtx_s* cctx, ZSTD_EndD return 0; } - /*! ZSTD_compressStream2() : + /*! ZSTD_compressStream2() : Requires v1.4.0+ * Behaves about the same as ZSTD_compressStream, with additional control on end directive. * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*() * - Compression parameters cannot be changed once compression is started (save a list of exceptions in multi-threading mode) @@ -6378,7 +7536,7 @@ private static nuint ZSTD_compressSequences_internal(ZSTD_CCtx_s* cctx, void* ds continue; } - compressedSeqsSize = ZSTD_entropyCompressSequences(&cctx->seqStore, &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, &cctx->appliedParams, (void*)(op + ZSTD_blockHeaderSize), dstCapacity - ZSTD_blockHeaderSize, blockSize, (void*)cctx->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(sizeof(uint)) * (uint)((((35) > (52) ? (35) : (52)) + 2)))), cctx->bmi2); + compressedSeqsSize = ZSTD_entropyCompressSeqStore(&cctx->seqStore, &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, &cctx->appliedParams, (void*)(op + ZSTD_blockHeaderSize), dstCapacity - ZSTD_blockHeaderSize, blockSize, (void*)cctx->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(sizeof(uint)) * (uint)((((35) > (52) ? (35) : (52)) + 2)))), cctx->bmi2); { nuint err_code = (compressedSeqsSize); @@ -6426,7 +7584,7 @@ private static nuint ZSTD_compressSequences_internal(ZSTD_CCtx_s* cctx, void* ds { uint cBlockHeader; - ZSTD_confirmRepcodesAndEntropyTables(cctx); + ZSTD_blockState_confirmRepcodesAndEntropyTables(&cctx->blockState); if (cctx->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat.FSE_repeat_valid) { cctx->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat.FSE_repeat_check; @@ -6599,6 +7757,11 @@ public static int ZSTD_minCLevel() return (int)(-(1 << 17)); } + public static int ZSTD_defaultCLevel() + { + return 3; + } + public static ZSTD_compressionParameters[][] ZSTD_defaultCParameters = new ZSTD_compressionParameters[4][] { new ZSTD_compressionParameters[23] @@ -7569,7 +8732,7 @@ private static ZSTD_compressionParameters ZSTD_dedicatedDictSearch_getCParams(in private static int ZSTD_dedicatedDictSearch_isSupported(ZSTD_compressionParameters* cParams) { - return (((cParams->strategy >= ZSTD_strategy.ZSTD_greedy) && (cParams->strategy <= ZSTD_strategy.ZSTD_lazy2)) ? 1 : 0); + return (((cParams->strategy >= ZSTD_strategy.ZSTD_greedy) && (cParams->strategy <= ZSTD_strategy.ZSTD_lazy2) && (cParams->hashLog > cParams->chainLog) && (cParams->chainLog <= 24)) ? 1 : 0); } /** @@ -7594,6 +8757,11 @@ private static void ZSTD_dedicatedDictSearch_revertCParams(ZSTD_compressionParam cParams->hashLog -= 2; } + if (cParams->hashLog < 6) + { + cParams->hashLog = 6; + } + break; case ZSTD_strategy.ZSTD_btlazy2: case ZSTD_strategy.ZSTD_btopt: diff --git a/src/ZstdSharp/Unsafe/ZstdCompressInternal.cs b/src/ZstdSharp/Unsafe/ZstdCompressInternal.cs index f6ce2e0..f8aa979 100644 --- a/src/ZstdSharp/Unsafe/ZstdCompressInternal.cs +++ b/src/ZstdSharp/Unsafe/ZstdCompressInternal.cs @@ -9,7 +9,7 @@ public static unsafe partial class Methods { public static readonly rawSeqStore_t kNullRawSeqStore = new rawSeqStore_t { - seq = null, + seq = (rawSeq*)null, pos = 0, posInSequence = 0, size = 0, @@ -17,7 +17,6 @@ public static unsafe partial class Methods }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static uint ZSTD_LLcode(uint litLength) { @@ -30,7 +29,6 @@ private static uint ZSTD_LLcode(uint litLength) * note : mlBase = matchLength - MINMATCH; * because it's the format it's stored in seqStore->sequences */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static uint ZSTD_MLcode(uint mlBase) { @@ -228,8 +226,8 @@ private static void ZSTD_storeSeq(seqStore_t* seqStorePtr, nuint litLength, byte seqStorePtr->lit += litLength; if (litLength > 0xFFFF) { - assert(seqStorePtr->longLengthID == 0); - seqStorePtr->longLengthID = 1; + assert(seqStorePtr->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_none); + seqStorePtr->longLengthType = ZSTD_longLengthType_e.ZSTD_llt_literalLength; seqStorePtr->longLengthPos = (uint)(seqStorePtr->sequences - seqStorePtr->sequencesStart); } @@ -237,8 +235,8 @@ private static void ZSTD_storeSeq(seqStore_t* seqStorePtr, nuint litLength, byte seqStorePtr->sequences[0].offset = offCode + 1; if (mlBase > 0xFFFF) { - assert(seqStorePtr->longLengthID == 0); - seqStorePtr->longLengthID = 2; + assert(seqStorePtr->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_none); + seqStorePtr->longLengthType = ZSTD_longLengthType_e.ZSTD_llt_matchLength; seqStorePtr->longLengthPos = (uint)(seqStorePtr->sequences - seqStorePtr->sequencesStart); } @@ -252,6 +250,11 @@ private static void ZSTD_storeSeq(seqStore_t* seqStorePtr, nuint litLength, byte [InlineMethod.Inline] private static uint ZSTD_NbCommonBytes(nuint val) { + if (val == 0) + { + return 0; + } + if (BitConverter.IsLittleEndian) { return (uint)(BitOperations.TrailingZeroCount(val) >> 3); @@ -537,6 +540,12 @@ private static void ZSTD_window_clear(ZSTD_window_t* window) window->dictLimit = end; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ZSTD_window_isEmpty(ZSTD_window_t window) + { + return ((window.dictLimit == 1 && window.lowLimit == 1 && (window.nextSrc - window.@base) == 1) ? 1U : 0U); + } + /** * ZSTD_window_hasExtDict(): * Returns non-zero if the window has a non-empty extDict. @@ -558,16 +567,36 @@ private static ZSTD_dictMode_e ZSTD_matchState_dictMode(ZSTD_matchState_t* ms) return (ZSTD_window_hasExtDict(ms->window)) != 0 ? ZSTD_dictMode_e.ZSTD_extDict : ms->dictMatchState != null ? (ms->dictMatchState->dedicatedDictSearch != 0 ? ZSTD_dictMode_e.ZSTD_dedicatedDictSearch : ZSTD_dictMode_e.ZSTD_dictMatchState) : ZSTD_dictMode_e.ZSTD_noDict; } + /** + * ZSTD_window_canOverflowCorrect(): + * Returns non-zero if the indices are large enough for overflow correction + * to work correctly without impacting compression ratio. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ZSTD_window_canOverflowCorrect(ZSTD_window_t window, uint cycleLog, uint maxDist, uint loadedDictEnd, void* src) + { + uint cycleSize = 1U << (int)cycleLog; + uint curr = (uint)((byte*)(src) - window.@base); + uint minIndexToOverflowCorrect = cycleSize + ((maxDist) > (cycleSize) ? (maxDist) : (cycleSize)); + uint adjustment = window.nbOverflowCorrections + 1; + uint adjustedIndex = ((minIndexToOverflowCorrect * adjustment) > (minIndexToOverflowCorrect) ? (minIndexToOverflowCorrect * adjustment) : (minIndexToOverflowCorrect)); + uint indexLargeEnough = ((curr > adjustedIndex) ? 1U : 0U); + uint dictionaryInvalidated = ((curr > maxDist + loadedDictEnd) ? 1U : 0U); + + return ((indexLargeEnough != 0 && dictionaryInvalidated != 0) ? 1U : 0U); + } + /** * ZSTD_window_needOverflowCorrection(): * Returns non-zero if the indices are getting too large and need overflow * protection. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint ZSTD_window_needOverflowCorrection(ZSTD_window_t window, void* srcEnd) + private static uint ZSTD_window_needOverflowCorrection(ZSTD_window_t window, uint cycleLog, uint maxDist, uint loadedDictEnd, void* src, void* srcEnd) { uint curr = (uint)((byte*)(srcEnd) - window.@base); + ; return ((curr > ((3U << 29) + (1U << ((int)((nuint)(sizeof(nuint)) == 4 ? 30 : 31))))) ? 1U : 0U); } @@ -579,21 +608,26 @@ private static uint ZSTD_window_needOverflowCorrection(ZSTD_window_t window, voi * * The least significant cycleLog bits of the indices must remain the same, * which may be 0. Every index up to maxDist in the past must be valid. - * NOTE: (maxDist & cycleMask) must be zero. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint ZSTD_window_correctOverflow(ZSTD_window_t* window, uint cycleLog, uint maxDist, void* src) { - uint cycleMask = (1U << (int)cycleLog) - 1; + uint cycleSize = 1U << (int)cycleLog; + uint cycleMask = cycleSize - 1; uint curr = (uint)((byte*)(src) - window->@base); uint currentCycle0 = curr & cycleMask; - uint currentCycle1 = currentCycle0 == 0 ? (1U << (int)cycleLog) : currentCycle0; - uint newCurrent = currentCycle1 + maxDist; + uint currentCycle1 = currentCycle0 == 0 ? cycleSize : currentCycle0; + uint newCurrent = currentCycle1 + ((maxDist) > (cycleSize) ? (maxDist) : (cycleSize)); uint correction = curr - newCurrent; - assert((maxDist & cycleMask) == 0); + assert((maxDist & (maxDist - 1)) == 0); + assert((curr & cycleMask) == (newCurrent & cycleMask)); assert(curr > newCurrent); - assert(correction > (uint)(1 << 28)); + if (0 == 0) + { + assert(correction > (uint)(1 << 28)); + } + window->@base += correction; window->dictBase += correction; if (window->lowLimit <= correction) @@ -618,6 +652,7 @@ private static uint ZSTD_window_correctOverflow(ZSTD_window_t* window, uint cycl assert(newCurrent - maxDist >= 1); assert(window->lowLimit <= newCurrent); assert(window->dictLimit <= newCurrent); + ++window->nbOverflowCorrections; return correction; } @@ -717,6 +752,7 @@ private static void ZSTD_window_init(ZSTD_window_t* window) window->dictLimit = 1; window->lowLimit = 1; window->nextSrc = window->@base + 1; + window->nbOverflowCorrections = 0; } /** @@ -727,7 +763,7 @@ private static void ZSTD_window_init(ZSTD_window_t* window) * Returns non-zero if the segment is contiguous. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint ZSTD_window_update(ZSTD_window_t* window, void* src, nuint srcSize) + private static uint ZSTD_window_update(ZSTD_window_t* window, void* src, nuint srcSize, int forceNonContiguous) { byte* ip = (byte*)(src); uint contiguous = 1; @@ -739,7 +775,7 @@ private static uint ZSTD_window_update(ZSTD_window_t* window, void* src, nuint s assert(window->@base != null); assert(window->dictBase != null); - if (src != window->nextSrc) + if (src != window->nextSrc || forceNonContiguous != 0) { nuint distanceFromBase = (nuint)(window->nextSrc - window->@base); @@ -772,6 +808,7 @@ private static uint ZSTD_window_update(ZSTD_window_t* window, void* src, nuint s * Returns the lowest allowed match index. It may either be in the ext-dict or the prefix. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] private static uint ZSTD_getLowestMatchIndex(ZSTD_matchState_t* ms, uint curr, uint windowLog) { uint maxDistance = 1U << (int)windowLog; @@ -787,7 +824,6 @@ private static uint ZSTD_getLowestMatchIndex(ZSTD_matchState_t* ms, uint curr, u * Returns the lowest allowed match index in the prefix. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static uint ZSTD_getLowestPrefixIndex(ZSTD_matchState_t* ms, uint curr, uint windowLog) { uint maxDistance = 1U << (int)windowLog; diff --git a/src/ZstdSharp/Unsafe/ZstdCompressLiterals.cs b/src/ZstdSharp/Unsafe/ZstdCompressLiterals.cs index 3cfc4b5..402aabc 100644 --- a/src/ZstdSharp/Unsafe/ZstdCompressLiterals.cs +++ b/src/ZstdSharp/Unsafe/ZstdCompressLiterals.cs @@ -129,7 +129,7 @@ public static nuint ZSTD_compressLiterals(ZSTD_hufCTables_t* prevHuf, ZSTD_hufCT } } - if (((uint)(((((cLitSize == 0) || (cLitSize >= srcSize - minGain))) ? 1 : 0)) | ERR_isError(cLitSize)) != 0) + if ((cLitSize == 0) || (cLitSize >= srcSize - minGain) || (ERR_isError(cLitSize)) != 0) { memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); diff --git a/src/ZstdSharp/Unsafe/ZstdCompressSequences.cs b/src/ZstdSharp/Unsafe/ZstdCompressSequences.cs index 68b4837..2ae887c 100644 --- a/src/ZstdSharp/Unsafe/ZstdCompressSequences.cs +++ b/src/ZstdSharp/Unsafe/ZstdCompressSequences.cs @@ -316,6 +316,7 @@ private static nuint ZSTD_entropyCost(uint* count, uint max, nuint total) uint cost = 0; uint s; + assert(total > 0); for (s = 0; s <= max; ++s) { uint norm = (uint)((256 * count[s]) / total); @@ -516,7 +517,7 @@ public static nuint ZSTD_buildCTable(void* dst, nuint dstCapacity, uint* nextCTa return 0; case symbolEncodingType_e.set_compressed: { - short* norm = stackalloc short[53]; + ZSTD_BuildCTableWksp* wksp = (ZSTD_BuildCTableWksp*)(entropyWorkspace); nuint nbSeq_1 = nbSeq; uint tableLog = FSE_optimalTableLog(FSELog, nbSeq, max); @@ -527,10 +528,10 @@ public static nuint ZSTD_buildCTable(void* dst, nuint dstCapacity, uint* nextCTa } assert(nbSeq_1 > 1); - assert(entropyWorkspaceSize >= ((nuint)(sizeof(uint)) * ((uint)(((35) > (52) ? (35) : (52)) + 2) + (1UL << (((((9) > (9) ? (9) : (9))) > (8) ? (((9) > (9) ? (9) : (9))) : (8)) - 2))))); + assert(entropyWorkspaceSize >= (nuint)(sizeof(ZSTD_BuildCTableWksp))); { - nuint err_code = (FSE_normalizeCount((short*)norm, tableLog, count, nbSeq_1, max, ZSTD_useLowProbCount(nbSeq_1))); + nuint err_code = (FSE_normalizeCount((short*)wksp->norm, tableLog, count, nbSeq_1, max, ZSTD_useLowProbCount(nbSeq_1))); if ((ERR_isError(err_code)) != 0) { @@ -540,7 +541,7 @@ public static nuint ZSTD_buildCTable(void* dst, nuint dstCapacity, uint* nextCTa { - nuint NCountSize = FSE_writeNCount((void*)op, (nuint)(oend - op), (short*)norm, max, tableLog); + nuint NCountSize = FSE_writeNCount((void*)op, (nuint)(oend - op), (short*)wksp->norm, max, tableLog); { @@ -554,7 +555,7 @@ public static nuint ZSTD_buildCTable(void* dst, nuint dstCapacity, uint* nextCTa { - nuint err_code = (FSE_buildCTable_wksp(nextCTable, (short*)norm, max, tableLog, entropyWorkspace, entropyWorkspaceSize)); + nuint err_code = (FSE_buildCTable_wksp(nextCTable, (short*)wksp->norm, max, tableLog, (void*)wksp->wksp, (nuint)(728))); if ((ERR_isError(err_code)) != 0) { diff --git a/src/ZstdSharp/Unsafe/ZstdCompressSuperblock.cs b/src/ZstdSharp/Unsafe/ZstdCompressSuperblock.cs index cf9eabf..a6aca19 100644 --- a/src/ZstdSharp/Unsafe/ZstdCompressSuperblock.cs +++ b/src/ZstdSharp/Unsafe/ZstdCompressSuperblock.cs @@ -5,288 +5,6 @@ namespace ZstdSharp.Unsafe { public static unsafe partial class Methods { - /** ZSTD_buildSuperBlockEntropy_literal() : - * Builds entropy for the super-block literals. - * Stores literals block type (raw, rle, compressed, repeat) and - * huffman description table to hufMetadata. - * @return : size of huffman description table or error code */ - private static nuint ZSTD_buildSuperBlockEntropy_literal(void* src, nuint srcSize, ZSTD_hufCTables_t* prevHuf, ZSTD_hufCTables_t* nextHuf, ZSTD_hufCTablesMetadata_t* hufMetadata, int disableLiteralsCompression, void* workspace, nuint wkspSize) - { - byte* wkspStart = (byte*)(workspace); - byte* wkspEnd = wkspStart + wkspSize; - byte* countWkspStart = wkspStart; - uint* countWksp = (uint*)(workspace); - nuint countWkspSize = (uint)((255 + 1)) * (nuint)(4); - byte* nodeWksp = countWkspStart + countWkspSize; - nuint nodeWkspSize = (nuint)(wkspEnd - nodeWksp); - uint maxSymbolValue = 255; - uint huffLog = 11; - HUF_repeat repeat = prevHuf->repeatMode; - - memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); - if (disableLiteralsCompression != 0) - { - hufMetadata->hType = symbolEncodingType_e.set_basic; - return 0; - } - - - { - nuint minLitSize = (nuint)((prevHuf->repeatMode == HUF_repeat.HUF_repeat_valid) ? 6 : 63); - - if (srcSize <= minLitSize) - { - hufMetadata->hType = symbolEncodingType_e.set_basic; - return 0; - } - } - - - { - nuint largest = HIST_count_wksp(countWksp, &maxSymbolValue, (void*)(byte*)(src), srcSize, workspace, wkspSize); - - - { - nuint err_code = (largest); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - if (largest == srcSize) - { - hufMetadata->hType = symbolEncodingType_e.set_rle; - return 0; - } - - if (largest <= (srcSize >> 7) + 4) - { - hufMetadata->hType = symbolEncodingType_e.set_basic; - return 0; - } - } - - if (repeat == HUF_repeat.HUF_repeat_check && (HUF_validateCTable((HUF_CElt_s*)(prevHuf->CTable), countWksp, maxSymbolValue)) == 0) - { - repeat = HUF_repeat.HUF_repeat_none; - } - - memset((void*)(nextHuf->CTable), (0), ((nuint)(sizeof(HUF_CElt_s) * 256))); - huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); - - { - nuint maxBits = HUF_buildCTable_wksp((HUF_CElt_s*)(nextHuf->CTable), countWksp, maxSymbolValue, huffLog, (void*)nodeWksp, nodeWkspSize); - - - { - nuint err_code = (maxBits); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - huffLog = (uint)(maxBits); - - { - nuint newCSize = HUF_estimateCompressedSize((HUF_CElt_s*)(nextHuf->CTable), countWksp, maxSymbolValue); - nuint hSize = HUF_writeCTable((void*)hufMetadata->hufDesBuffer, (nuint)(128), (HUF_CElt_s*)(nextHuf->CTable), maxSymbolValue, huffLog); - - if (repeat != HUF_repeat.HUF_repeat_none) - { - nuint oldCSize = HUF_estimateCompressedSize((HUF_CElt_s*)(prevHuf->CTable), countWksp, maxSymbolValue); - - if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) - { - memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); - hufMetadata->hType = symbolEncodingType_e.set_repeat; - return 0; - } - } - - if (newCSize + hSize >= srcSize) - { - memcpy((void*)(nextHuf), (void*)(prevHuf), ((nuint)(sizeof(ZSTD_hufCTables_t)))); - hufMetadata->hType = symbolEncodingType_e.set_basic; - return 0; - } - - hufMetadata->hType = symbolEncodingType_e.set_compressed; - nextHuf->repeatMode = HUF_repeat.HUF_repeat_check; - return hSize; - } - } - } - - /** ZSTD_buildSuperBlockEntropy_sequences() : - * Builds entropy for the super-block sequences. - * Stores symbol compression modes and fse table to fseMetadata. - * @return : size of fse tables or error code */ - private static nuint ZSTD_buildSuperBlockEntropy_sequences(seqStore_t* seqStorePtr, ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, ZSTD_fseCTablesMetadata_t* fseMetadata, void* workspace, nuint wkspSize) - { - byte* wkspStart = (byte*)(workspace); - byte* wkspEnd = wkspStart + wkspSize; - byte* countWkspStart = wkspStart; - uint* countWksp = (uint*)(workspace); - nuint countWkspSize = (uint)((((35) > (52) ? (35) : (52)) + 1)) * (nuint)(4); - byte* cTableWksp = countWkspStart + countWkspSize; - nuint cTableWkspSize = (nuint)(wkspEnd - cTableWksp); - ZSTD_strategy strategy = cctxParams->cParams.strategy; - uint* CTable_LitLength = (uint*)nextEntropy->litlengthCTable; - uint* CTable_OffsetBits = (uint*)nextEntropy->offcodeCTable; - uint* CTable_MatchLength = (uint*)nextEntropy->matchlengthCTable; - byte* ofCodeTable = seqStorePtr->ofCode; - byte* llCodeTable = seqStorePtr->llCode; - byte* mlCodeTable = seqStorePtr->mlCode; - nuint nbSeq = (nuint)(seqStorePtr->sequences - seqStorePtr->sequencesStart); - byte* ostart = (byte*)fseMetadata->fseTablesBuffer; - byte* oend = ostart + (nuint)(133); - byte* op = ostart; - - assert(cTableWkspSize >= (uint)((1 << ((((9) > (9) ? (9) : (9))) > (8) ? (((9) > (9) ? (9) : (9))) : (8)))) * (nuint)(sizeof(byte))); - memset((workspace), (0), (wkspSize)); - fseMetadata->lastCountSize = 0; - ZSTD_seqToCodes(seqStorePtr); - - { - uint LLtype; - uint max = 35; - nuint mostFrequent = HIST_countFast_wksp(countWksp, &max, (void*)llCodeTable, nbSeq, workspace, wkspSize); - - nextEntropy->litlength_repeatMode = prevEntropy->litlength_repeatMode; - LLtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->litlength_repeatMode, countWksp, max, mostFrequent, nbSeq, 9, (uint*)prevEntropy->litlengthCTable, (short*)LL_defaultNorm, LL_defaultNormLog, ZSTD_defaultPolicy_e.ZSTD_defaultAllowed, strategy)); - assert(symbolEncodingType_e.set_basic < symbolEncodingType_e.set_compressed && symbolEncodingType_e.set_rle < symbolEncodingType_e.set_compressed); - assert(!(LLtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->litlength_repeatMode != FSE_repeat.FSE_repeat_none)); - - { - nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_LitLength, 9, (symbolEncodingType_e)(LLtype), countWksp, max, llCodeTable, nbSeq, (short*)LL_defaultNorm, LL_defaultNormLog, 35, (uint*)prevEntropy->litlengthCTable, (nuint)(1316), (void*)cTableWksp, cTableWkspSize); - - - { - nuint err_code = (countSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - if (LLtype == (uint)symbolEncodingType_e.set_compressed) - { - fseMetadata->lastCountSize = countSize; - } - - op += countSize; - fseMetadata->llType = (symbolEncodingType_e)(LLtype); - } - } - - - { - uint Offtype; - uint max = 31; - nuint mostFrequent = HIST_countFast_wksp(countWksp, &max, (void*)ofCodeTable, nbSeq, workspace, wkspSize); - ZSTD_defaultPolicy_e defaultPolicy = (max <= 28) ? ZSTD_defaultPolicy_e.ZSTD_defaultAllowed : ZSTD_defaultPolicy_e.ZSTD_defaultDisallowed; - - nextEntropy->offcode_repeatMode = prevEntropy->offcode_repeatMode; - Offtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->offcode_repeatMode, countWksp, max, mostFrequent, nbSeq, 8, (uint*)prevEntropy->offcodeCTable, (short*)OF_defaultNorm, OF_defaultNormLog, defaultPolicy, strategy)); - assert(!(Offtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->offcode_repeatMode != FSE_repeat.FSE_repeat_none)); - - { - nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_OffsetBits, 8, (symbolEncodingType_e)(Offtype), countWksp, max, ofCodeTable, nbSeq, (short*)OF_defaultNorm, OF_defaultNormLog, 28, (uint*)prevEntropy->offcodeCTable, (nuint)(772), (void*)cTableWksp, cTableWkspSize); - - - { - nuint err_code = (countSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - if (Offtype == (uint)symbolEncodingType_e.set_compressed) - { - fseMetadata->lastCountSize = countSize; - } - - op += countSize; - fseMetadata->ofType = (symbolEncodingType_e)(Offtype); - } - } - - - { - uint MLtype; - uint max = 52; - nuint mostFrequent = HIST_countFast_wksp(countWksp, &max, (void*)mlCodeTable, nbSeq, workspace, wkspSize); - - nextEntropy->matchlength_repeatMode = prevEntropy->matchlength_repeatMode; - MLtype = (uint)(ZSTD_selectEncodingType(&nextEntropy->matchlength_repeatMode, countWksp, max, mostFrequent, nbSeq, 9, (uint*)prevEntropy->matchlengthCTable, (short*)ML_defaultNorm, ML_defaultNormLog, ZSTD_defaultPolicy_e.ZSTD_defaultAllowed, strategy)); - assert(!(MLtype < (uint)symbolEncodingType_e.set_compressed && nextEntropy->matchlength_repeatMode != FSE_repeat.FSE_repeat_none)); - - { - nuint countSize = ZSTD_buildCTable((void*)op, (nuint)(oend - op), CTable_MatchLength, 9, (symbolEncodingType_e)(MLtype), countWksp, max, mlCodeTable, nbSeq, (short*)ML_defaultNorm, ML_defaultNormLog, 52, (uint*)prevEntropy->matchlengthCTable, (nuint)(1452), (void*)cTableWksp, cTableWkspSize); - - - { - nuint err_code = (countSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - if (MLtype == (uint)symbolEncodingType_e.set_compressed) - { - fseMetadata->lastCountSize = countSize; - } - - op += countSize; - fseMetadata->mlType = (symbolEncodingType_e)(MLtype); - } - } - - assert((nuint)(op - ostart) <= (nuint)(sizeof(byte) * 133)); - return (nuint)(op - ostart); - } - - /** ZSTD_buildSuperBlockEntropy() : - * Builds entropy for the super-block. - * @return : 0 on success or error code */ - private static nuint ZSTD_buildSuperBlockEntropy(seqStore_t* seqStorePtr, ZSTD_entropyCTables_t* prevEntropy, ZSTD_entropyCTables_t* nextEntropy, ZSTD_CCtx_params_s* cctxParams, ZSTD_entropyCTablesMetadata_t* entropyMetadata, void* workspace, nuint wkspSize) - { - nuint litSize = (nuint)(seqStorePtr->lit - seqStorePtr->litStart); - - entropyMetadata->hufMetadata.hufDesSize = ZSTD_buildSuperBlockEntropy_literal((void*)seqStorePtr->litStart, litSize, &prevEntropy->huf, &nextEntropy->huf, &entropyMetadata->hufMetadata, ZSTD_disableLiteralsCompression(cctxParams), workspace, wkspSize); - - { - nuint err_code = (entropyMetadata->hufMetadata.hufDesSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - entropyMetadata->fseMetadata.fseTablesSize = ZSTD_buildSuperBlockEntropy_sequences(seqStorePtr, &prevEntropy->fse, &nextEntropy->fse, cctxParams, &entropyMetadata->fseMetadata, workspace, wkspSize); - - { - nuint err_code = (entropyMetadata->fseMetadata.fseTablesSize); - - if ((ERR_isError(err_code)) != 0) - { - return err_code; - } - } - - return 0; - } - /** ZSTD_compressSubBlock_literal() : * Compresses literals section for a sub-block. * When we have to write the Huffman table we will sometimes choose a header @@ -672,6 +390,11 @@ private static nuint ZSTD_estimateSubBlockSize_sequences(byte* ofCodeTable, byte nuint sequencesSectionHeaderSize = 3; nuint cSeqSizeEstimate = 0; + if (nbSeq == 0) + { + return sequencesSectionHeaderSize; + } + cSeqSizeEstimate += ZSTD_estimateSubBlockSize_symbolType(fseMetadata->ofType, ofCodeTable, 31, nbSeq, (uint*)fseTables->offcodeCTable, (uint*)null, (short*)OF_defaultNorm, OF_defaultNormLog, 28, workspace, wkspSize); cSeqSizeEstimate += ZSTD_estimateSubBlockSize_symbolType(fseMetadata->llType, llCodeTable, 35, nbSeq, (uint*)fseTables->litlengthCTable, (uint*)LL_bits, (short*)LL_defaultNorm, LL_defaultNormLog, 35, workspace, wkspSize); cSeqSizeEstimate += ZSTD_estimateSubBlockSize_symbolType(fseMetadata->mlType, mlCodeTable, 52, nbSeq, (uint*)fseTables->matchlengthCTable, (uint*)ML_bits, (short*)ML_defaultNorm, ML_defaultNormLog, 52, workspace, wkspSize); @@ -864,7 +587,7 @@ public static nuint ZSTD_compressSuperBlock(ZSTD_CCtx_s* zc, void* dst, nuint ds { - nuint err_code = (ZSTD_buildSuperBlockEntropy(&zc->seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, &entropyMetadata, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(4) * (uint)((((35) > (52) ? (35) : (52)) + 2)))))); + nuint err_code = (ZSTD_buildBlockEntropyStats(&zc->seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, &entropyMetadata, (void*)zc->entropyWorkspace, ((uint)(((6 << 10) + 256)) + ((nuint)(4) * (uint)((((35) > (52) ? (35) : (52)) + 2)))))); if ((ERR_isError(err_code)) != 0) { diff --git a/src/ZstdSharp/Unsafe/ZstdCwksp.cs b/src/ZstdSharp/Unsafe/ZstdCwksp.cs index 10a2a87..102c9b3 100644 --- a/src/ZstdSharp/Unsafe/ZstdCwksp.cs +++ b/src/ZstdSharp/Unsafe/ZstdCwksp.cs @@ -38,6 +38,8 @@ private static nuint ZSTD_cwksp_align(nuint size, nuint align) * Since tables aren't currently redzoned, you don't need to call through this * to figure out how much space you need for the matchState tables. Everything * else is though. + * + * Do not use for sizing aligned buffers. Instead, use ZSTD_cwksp_aligned_alloc_size(). */ [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint ZSTD_cwksp_alloc_size(nuint size) @@ -50,8 +52,79 @@ private static nuint ZSTD_cwksp_alloc_size(nuint size) return size; } + /** + * Returns an adjusted alloc size that is the nearest larger multiple of 64 bytes. + * Used to determine the number of bytes required for a given "aligned". + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_cwksp_aligned_alloc_size(nuint size) + { + return ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(size, 64)); + } + + /** + * Returns the amount of additional space the cwksp must allocate + * for internal purposes (currently only alignment). + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ZSTD_cwksp_internal_advance_phase(ZSTD_cwksp* ws, ZSTD_cwksp_alloc_phase_e phase) + private static nuint ZSTD_cwksp_slack_space_required() + { + nuint slackSpace = 64; + + return slackSpace; + } + + /** + * Return the number of additional bytes required to align a pointer to the given number of bytes. + * alignBytes must be a power of two. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_cwksp_bytes_to_align_ptr(void* ptr, nuint alignBytes) + { + nuint alignBytesMask = alignBytes - 1; + nuint bytes = (alignBytes - ((nuint)(ptr) & (alignBytesMask))) & alignBytesMask; + + assert((alignBytes & alignBytesMask) == 0); + assert(bytes != 64); + return bytes; + } + + /** + * Internal function. Do not use directly. + * Reserves the given number of bytes within the aligned/buffer segment of the wksp, which + * counts from the end of the wksp. (as opposed to the object/table segment) + * + * Returns a pointer to the beginning of that space. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void* ZSTD_cwksp_reserve_internal_buffer_space(ZSTD_cwksp* ws, nuint bytes) + { + void* alloc = (void*)((byte*)(ws->allocStart) - bytes); + void* bottom = ws->tableEnd; + + ZSTD_cwksp_assert_internal_consistency(ws); + assert(alloc >= bottom); + if (alloc < bottom) + { + ws->allocFailed = 1; + return null; + } + + if (alloc < ws->tableValidEnd) + { + ws->tableValidEnd = alloc; + } + + ws->allocStart = alloc; + return alloc; + } + + /** + * Moves the cwksp to the next phase, and does any necessary allocations. + * Returns a 0 on success, or zstd error + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_cwksp_internal_advance_phase(ZSTD_cwksp* ws, ZSTD_cwksp_alloc_phase_e phase) { assert(phase >= ws->phase); if (phase > ws->phase) @@ -63,15 +136,39 @@ private static void ZSTD_cwksp_internal_advance_phase(ZSTD_cwksp* ws, ZSTD_cwksp if (ws->phase < ZSTD_cwksp_alloc_phase_e.ZSTD_cwksp_alloc_aligned && phase >= ZSTD_cwksp_alloc_phase_e.ZSTD_cwksp_alloc_aligned) { - ws->allocStart = (byte*)(ws->allocStart) - ((nuint)(ws->allocStart) & ((nuint)(sizeof(uint)) - 1)); - if (ws->allocStart < ws->tableValidEnd) + { - ws->tableValidEnd = ws->allocStart; + nuint bytesToAlign = 64 - ZSTD_cwksp_bytes_to_align_ptr(ws->allocStart, 64); + + if ((ZSTD_cwksp_reserve_internal_buffer_space(ws, bytesToAlign)) == null) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_memory_allocation))); + } + + } + + + { + void* alloc = ws->objectEnd; + nuint bytesToAlign = ZSTD_cwksp_bytes_to_align_ptr(alloc, 64); + void* end = (void*)((byte*)(alloc) + bytesToAlign); + + if (end > ws->workspaceEnd) + { + return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_memory_allocation))); + } + + ws->objectEnd = end; + ws->tableEnd = end; + ws->tableValidEnd = end; } } ws->phase = phase; + ZSTD_cwksp_assert_internal_consistency(ws); } + + return 0; } /** @@ -90,29 +187,13 @@ private static int ZSTD_cwksp_owns_buffer(ZSTD_cwksp* ws, void* ptr) private static void* ZSTD_cwksp_reserve_internal(ZSTD_cwksp* ws, nuint bytes, ZSTD_cwksp_alloc_phase_e phase) { void* alloc; - void* bottom = ws->tableEnd; - ZSTD_cwksp_internal_advance_phase(ws, phase); - alloc = (byte*)(ws->allocStart) - bytes; - if (bytes == 0) + if ((ERR_isError(ZSTD_cwksp_internal_advance_phase(ws, phase))) != 0 || bytes == 0) { return null; } - ZSTD_cwksp_assert_internal_consistency(ws); - assert(alloc >= bottom); - if (alloc < bottom) - { - ws->allocFailed = 1; - return null; - } - - if (alloc < ws->tableValidEnd) - { - ws->tableValidEnd = alloc; - } - - ws->allocStart = alloc; + alloc = ZSTD_cwksp_reserve_internal_buffer_space(ws, bytes); return alloc; } @@ -126,17 +207,19 @@ private static int ZSTD_cwksp_owns_buffer(ZSTD_cwksp* ws, void* ptr) } /** - * Reserves and returns memory sized on and aligned on sizeof(unsigned). + * Reserves and returns memory sized on and aligned on ZSTD_CWKSP_ALIGNMENT_BYTES (64 bytes). */ [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, nuint bytes) { - assert((bytes & ((nuint)(sizeof(uint)) - 1)) == 0); - return ZSTD_cwksp_reserve_internal(ws, ZSTD_cwksp_align(bytes, (nuint)(sizeof(uint))), ZSTD_cwksp_alloc_phase_e.ZSTD_cwksp_alloc_aligned); + void* ptr = ZSTD_cwksp_reserve_internal(ws, ZSTD_cwksp_align(bytes, 64), ZSTD_cwksp_alloc_phase_e.ZSTD_cwksp_alloc_aligned); + + assert(((nuint)(ptr) & (uint)((64 - 1))) == 0); + return ptr; } /** - * Aligned on sizeof(unsigned). These buffers have the special property that + * Aligned on 64 bytes. These buffers have the special property that * their values remain constrained, allowing us to re-use them without * memset()-ing them. */ @@ -144,12 +227,19 @@ private static int ZSTD_cwksp_owns_buffer(ZSTD_cwksp* ws, void* ptr) private static void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, nuint bytes) { ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_phase_e.ZSTD_cwksp_alloc_aligned; - void* alloc = ws->tableEnd; - void* end = (void*)((byte*)(alloc) + bytes); - void* top = ws->allocStart; + void* alloc; + void* end; + void* top; + + if ((ERR_isError(ZSTD_cwksp_internal_advance_phase(ws, phase))) != 0) + { + return null; + } + alloc = ws->tableEnd; + end = (byte*)(alloc) + bytes; + top = ws->allocStart; assert((bytes & ((nuint)(sizeof(uint)) - 1)) == 0); - ZSTD_cwksp_internal_advance_phase(ws, phase); ZSTD_cwksp_assert_internal_consistency(ws); assert(end <= top); if (end > top) @@ -159,6 +249,8 @@ private static int ZSTD_cwksp_owns_buffer(ZSTD_cwksp* ws, void* ptr) } ws->tableEnd = end; + assert((bytes & (uint)((64 - 1))) == 0); + assert(((nuint)(alloc) & (uint)((64 - 1))) == 0); return alloc; } @@ -326,8 +418,25 @@ private static int ZSTD_cwksp_reserve_failed(ZSTD_cwksp* ws) return (int)ws->allocFailed; } + /* ZSTD_alignmentSpaceWithinBounds() : + * Returns if the estimated space needed for a wksp is within an acceptable limit of the + * actual amount of space used. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ZSTD_cwksp_estimated_space_within_bounds(ZSTD_cwksp* ws, nuint estimatedSpace, int resizedWorkspace) + { + if (resizedWorkspace != 0) + { + return ((ZSTD_cwksp_used(ws) == estimatedSpace) ? 1 : 0); + } + else + { + return (((ZSTD_cwksp_used(ws) >= estimatedSpace - 63) && (ZSTD_cwksp_used(ws) <= estimatedSpace + 63)) ? 1 : 0); + } + } + /*-************************************* - * Functions Checking Free Space + * Functions ***************************************/ [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint ZSTD_cwksp_available_space(ZSTD_cwksp* ws) diff --git a/src/ZstdSharp/Unsafe/ZstdDdict.cs b/src/ZstdSharp/Unsafe/ZstdDdict.cs index 1c06c51..b878db5 100644 --- a/src/ZstdSharp/Unsafe/ZstdDdict.cs +++ b/src/ZstdSharp/Unsafe/ZstdDdict.cs @@ -235,7 +235,8 @@ private static nuint ZSTD_initDDict_internal(ZSTD_DDict_s* ddict, void* dict, nu } /*! ZSTD_freeDDict() : - * Function frees memory allocated with ZSTD_createDDict() */ + * Function frees memory allocated with ZSTD_createDDict() + * If a NULL pointer is passed, no operation is performed. */ public static nuint ZSTD_freeDDict(ZSTD_DDict_s* ddict) { if (ddict == null) diff --git a/src/ZstdSharp/Unsafe/ZstdDecompress.cs b/src/ZstdSharp/Unsafe/ZstdDecompress.cs index 6359f27..ad67ae3 100644 --- a/src/ZstdSharp/Unsafe/ZstdDecompress.cs +++ b/src/ZstdSharp/Unsafe/ZstdDecompress.cs @@ -2045,7 +2045,7 @@ public static nuint ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx_s* dctx, void return ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dictSize, ZSTD_dictLoadMethod_e.ZSTD_dlm_byRef, ZSTD_dictContentType_e.ZSTD_dct_auto); } - /*! ZSTD_DCtx_loadDictionary() : + /*! ZSTD_DCtx_loadDictionary() : Requires v1.4.0+ * Create an internal DDict from dict buffer, * to be used to decompress next frames. * The dictionary remains valid for all future frames, until explicitly invalidated. @@ -2084,7 +2084,7 @@ public static nuint ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx_s* dctx, void* prefix return 0; } - /*! ZSTD_DCtx_refPrefix() : + /*! ZSTD_DCtx_refPrefix() : Requires v1.4.0+ * Reference a prefix (single-usage dictionary) to decompress next frame. * This is the reverse operation of ZSTD_CCtx_refPrefix(), * and must use the same prefix as the one used during compression. @@ -2185,7 +2185,7 @@ public static nuint ZSTD_resetDStream(ZSTD_DCtx_s* dctx) return ZSTD_startingInputLength(dctx->format); } - /*! ZSTD_DCtx_refDDict() : + /*! ZSTD_DCtx_refDDict() : Requires v1.4.0+ * Reference a prepared dictionary, to be used to decompress next frames. * The dictionary remains active for decompression of future frames using same DCtx. * @@ -2274,6 +2274,7 @@ public static nuint ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx_s* dctx, nuint maxWindo } /*! ZSTD_DCtx_setFormat() : + * This function is REDUNDANT. Prefer ZSTD_DCtx_setParameter(). * Instruct the decoder context about what kind of data to decode next. * This instruction is mandatory to decode data without a fully-formed header, * such ZSTD_f_zstd1_magicless for example. diff --git a/src/ZstdSharp/Unsafe/ZstdDecompressBlock.cs b/src/ZstdSharp/Unsafe/ZstdDecompressBlock.cs index c9c39b1..19332b5 100644 --- a/src/ZstdSharp/Unsafe/ZstdDecompressBlock.cs +++ b/src/ZstdSharp/Unsafe/ZstdDecompressBlock.cs @@ -9,7 +9,6 @@ public static unsafe partial class Methods /*_******************************************************* * Memory operations **********************************************************/ - [InlineMethod.Inline] private static void ZSTD_copy4(void* dst, void* src) { memcpy((dst), (src), (4)); @@ -160,11 +159,11 @@ public static nuint ZSTD_decodeLiteralsBlock(ZSTD_DCtx_s* dctx, void* src, nuint { if (singleStream != 0) { - hufSuccess = HUF_decompress1X1_DCtx_wksp_bmi2((uint*)dctx->entropy.hufTable, (void*)dctx->litBuffer, litSize, (void*)(istart + lhSize), litCSize, (void*)dctx->workspace, (nuint)(sizeof(uint) * 512), dctx->bmi2); + hufSuccess = HUF_decompress1X1_DCtx_wksp_bmi2((uint*)dctx->entropy.hufTable, (void*)dctx->litBuffer, litSize, (void*)(istart + lhSize), litCSize, (void*)dctx->workspace, (nuint)(sizeof(uint) * 640), dctx->bmi2); } else { - hufSuccess = HUF_decompress4X_hufOnly_wksp_bmi2((uint*)dctx->entropy.hufTable, (void*)dctx->litBuffer, litSize, (void*)(istart + lhSize), litCSize, (void*)dctx->workspace, (nuint)(sizeof(uint) * 512), dctx->bmi2); + hufSuccess = HUF_decompress4X_hufOnly_wksp_bmi2((uint*)dctx->entropy.hufTable, (void*)dctx->litBuffer, litSize, (void*)(istart + lhSize), litCSize, (void*)dctx->workspace, (nuint)(sizeof(uint) * 640), dctx->bmi2); } } @@ -1805,7 +1804,7 @@ public static nuint ZSTD_decodeSeqHeaders(ZSTD_DCtx_s* dctx, int* nbSeqPtr, void ip++; { - nuint llhSize = ZSTD_buildSeqTable((ZSTD_seqSymbol*)dctx->entropy.LLTable, &dctx->LLTptr, LLtype, 35, 9, (void*)ip, (nuint)(iend - ip), (uint*)LL_base, (uint*)LL_bits, (ZSTD_seqSymbol*)LL_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, (uint*)dctx->workspace, (nuint)(2048), dctx->bmi2); + nuint llhSize = ZSTD_buildSeqTable((ZSTD_seqSymbol*)dctx->entropy.LLTable, &dctx->LLTptr, LLtype, 35, 9, (void*)ip, (nuint)(iend - ip), (uint*)LL_base, (uint*)LL_bits, (ZSTD_seqSymbol*)LL_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, (uint*)dctx->workspace, (nuint)(2560), dctx->bmi2); if ((ERR_isError(llhSize)) != 0) { @@ -1817,7 +1816,7 @@ public static nuint ZSTD_decodeSeqHeaders(ZSTD_DCtx_s* dctx, int* nbSeqPtr, void { - nuint ofhSize = ZSTD_buildSeqTable((ZSTD_seqSymbol*)dctx->entropy.OFTable, &dctx->OFTptr, OFtype, 31, 8, (void*)ip, (nuint)(iend - ip), (uint*)OF_base, (uint*)OF_bits, (ZSTD_seqSymbol*)OF_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, (uint*)dctx->workspace, (nuint)(2048), dctx->bmi2); + nuint ofhSize = ZSTD_buildSeqTable((ZSTD_seqSymbol*)dctx->entropy.OFTable, &dctx->OFTptr, OFtype, 31, 8, (void*)ip, (nuint)(iend - ip), (uint*)OF_base, (uint*)OF_bits, (ZSTD_seqSymbol*)OF_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, (uint*)dctx->workspace, (nuint)(2560), dctx->bmi2); if ((ERR_isError(ofhSize)) != 0) { @@ -1829,7 +1828,7 @@ public static nuint ZSTD_decodeSeqHeaders(ZSTD_DCtx_s* dctx, int* nbSeqPtr, void { - nuint mlhSize = ZSTD_buildSeqTable((ZSTD_seqSymbol*)dctx->entropy.MLTable, &dctx->MLTptr, MLtype, 52, 9, (void*)ip, (nuint)(iend - ip), (uint*)ML_base, (uint*)ML_bits, (ZSTD_seqSymbol*)ML_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, (uint*)dctx->workspace, (nuint)(2048), dctx->bmi2); + nuint mlhSize = ZSTD_buildSeqTable((ZSTD_seqSymbol*)dctx->entropy.MLTable, &dctx->MLTptr, MLtype, 52, 9, (void*)ip, (nuint)(iend - ip), (uint*)ML_base, (uint*)ML_bits, (ZSTD_seqSymbol*)ML_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, (uint*)dctx->workspace, (nuint)(2560), dctx->bmi2); if ((ERR_isError(mlhSize)) != 0) { @@ -1851,7 +1850,6 @@ public static nuint ZSTD_decodeSeqHeaders(ZSTD_DCtx_s* dctx, int* nbSeqPtr, void * Postcondition: *op - *op >= 8 */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void ZSTD_overlapCopy8(byte** op, byte** ip, nuint offset) { assert(*ip <= *op); @@ -1995,7 +1993,6 @@ private static nuint ZSTD_execSequenceEnd(byte* op, byte* oend, seq_t sequence, } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static nuint ZSTD_execSequence(byte* op, byte* oend, seq_t sequence, byte** litPtr, byte* litLimit, byte* prefixStart, byte* virtualStart, byte* dictEnd) { byte* oLitEnd = op + sequence.litLength; @@ -2084,7 +2081,6 @@ private static void ZSTD_initFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* b } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void ZSTD_updateFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD) { ZSTD_seqSymbol DInfo = DStatePtr->table[DStatePtr->state]; @@ -2095,7 +2091,6 @@ private static void ZSTD_updateFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void ZSTD_updateFseStateWithDInfo(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, ZSTD_seqSymbol DInfo) { uint nbBits = DInfo.nbBits; @@ -2106,7 +2101,7 @@ private static void ZSTD_updateFseStateWithDInfo(ZSTD_fseState* DStatePtr, BIT_D [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - private static seq_t ZSTD_decodeSequence(seqState_t* seqState, ZSTD_longOffset_e longOffsets, ZSTD_prefetch_e prefetch) + private static seq_t ZSTD_decodeSequence(seqState_t* seqState, ZSTD_longOffset_e longOffsets) { seq_t seq; var _ = &seq; @@ -2220,15 +2215,6 @@ private static seq_t ZSTD_decodeSequence(seqState_t* seqState, ZSTD_longOffset_e BIT_reloadDStream(&seqState->DStream); } - if (prefetch == ZSTD_prefetch_e.ZSTD_p_prefetch) - { - nuint pos = seqState->pos + seq.litLength; - byte* matchBase = (seq.offset > pos) ? seqState->dictEnd : seqState->prefixStart; - - seq.match = matchBase + pos - seq.offset; - seqState->pos = pos + seq.matchLength; - } - { int kUseUpdateFseState = 0; @@ -2277,8 +2263,7 @@ private static nuint ZSTD_decompressSequences_body(ZSTD_DCtx_s* dctx, void* dst, if (nbSeq != 0) { seqState_t seqState; - var _ = &seqState; - nuint error = 0; + var _ = &seqState; dctx->fseEntropy = 1; @@ -2302,26 +2287,21 @@ private static nuint ZSTD_decompressSequences_body(ZSTD_DCtx_s* dctx, void* dst, assert(dst != null); for (;;) { - seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset, ZSTD_prefetch_e.ZSTD_p_noPrefetch); + seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset); nuint oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litEnd, prefixStart, vBase, dictEnd); - BIT_reloadDStream(&(seqState.DStream)); - op += oneSeqSize; if ((ERR_isError(oneSeqSize)) != 0) { - error = oneSeqSize; - break; + return oneSeqSize; } + op += oneSeqSize; if (--nbSeq == 0) { break; } - } - if ((ERR_isError(error)) != 0) - { - return error; + BIT_reloadDStream(&(seqState.DStream)); } if (nbSeq != 0) @@ -2369,6 +2349,26 @@ private static nuint ZSTD_decompressSequences_default(ZSTD_DCtx_s* dctx, void* d return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_prefetchMatch(nuint prefetchPos, seq_t sequence, byte* prefixStart, byte* dictEnd) + { + prefetchPos += sequence.litLength; + + { + byte* matchBase = (sequence.offset > prefetchPos) ? dictEnd : prefixStart; + byte* match = matchBase + prefetchPos - sequence.offset; + + Prefetch0((void*)match); + Prefetch0((void*)(match + 64)); + } + + return prefetchPos + sequence.matchLength; + } + + /* This decoding function employs prefetching + * to reduce latency impact of cache misses. + * It's generally employed when block contains a significant portion of long-distance matches + * or when coupled with a "cold" dictionary */ [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint ZSTD_decompressSequencesLong_body(ZSTD_DCtx_s* dctx, void* dst, nuint maxDstSize, void* seqStart, nuint seqSize, int nbSeq, ZSTD_longOffset_e isLongOffset, int frame) { @@ -2385,11 +2385,12 @@ private static nuint ZSTD_decompressSequencesLong_body(ZSTD_DCtx_s* dctx, void* if (nbSeq != 0) { - seq_t* sequences = stackalloc seq_t[4]; - int seqAdvance = ((nbSeq) < (4) ? (nbSeq) : (4)); + seq_t* sequences = stackalloc seq_t[8]; + int seqAdvance = ((nbSeq) < (8) ? (nbSeq) : (8)); seqState_t seqState; var _ = &seqState; int seqNb; + nuint prefetchPos = (nuint)(op - prefixStart); dctx->fseEntropy = 1; @@ -2402,9 +2403,6 @@ private static nuint ZSTD_decompressSequencesLong_body(ZSTD_DCtx_s* dctx, void* } } - seqState.prefixStart = prefixStart; - seqState.pos = (nuint)(op - prefixStart); - seqState.dictEnd = dictEnd; assert(dst != null); assert(iend >= ip); if ((ERR_isError(BIT_initDStream(&seqState.DStream, (void*)ip, (nuint)(iend - ip)))) != 0) @@ -2417,9 +2415,10 @@ private static nuint ZSTD_decompressSequencesLong_body(ZSTD_DCtx_s* dctx, void* ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr); for (seqNb = 0; (BIT_reloadDStream(&seqState.DStream) <= BIT_DStream_status.BIT_DStream_completed) && (seqNb < seqAdvance); seqNb++) { - sequences[seqNb] = ZSTD_decodeSequence(&seqState, isLongOffset, ZSTD_prefetch_e.ZSTD_p_prefetch); - Prefetch0((void*)(sequences[seqNb].match)); - Prefetch0((void*)(sequences[seqNb].match + sequences[seqNb].matchLength - 1)); + seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + + prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); + sequences[seqNb] = sequence; } if (seqNb < seqAdvance) @@ -2429,17 +2428,16 @@ private static nuint ZSTD_decompressSequencesLong_body(ZSTD_DCtx_s* dctx, void* for (; (BIT_reloadDStream(&(seqState.DStream)) <= BIT_DStream_status.BIT_DStream_completed) && (seqNb < nbSeq); seqNb++) { - seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset, ZSTD_prefetch_e.ZSTD_p_prefetch); - nuint oneSeqSize = ZSTD_execSequence(op, oend, sequences[(seqNb - 4) & (4 - 1)], &litPtr, litEnd, prefixStart, dictStart, dictEnd); + seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + nuint oneSeqSize = ZSTD_execSequence(op, oend, sequences[(seqNb - 8) & (8 - 1)], &litPtr, litEnd, prefixStart, dictStart, dictEnd); if ((ERR_isError(oneSeqSize)) != 0) { return oneSeqSize; } - Prefetch0((void*)sequence.match); - Prefetch0((void*)(sequence.match + sequence.matchLength - 1)); - sequences[seqNb & (4 - 1)] = sequence; + prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); + sequences[seqNb & (8 - 1)] = sequence; op += oneSeqSize; } @@ -2451,7 +2449,7 @@ private static nuint ZSTD_decompressSequencesLong_body(ZSTD_DCtx_s* dctx, void* seqNb -= seqAdvance; for (; seqNb < nbSeq; seqNb++) { - nuint oneSeqSize = ZSTD_execSequence(op, oend, sequences[seqNb & (4 - 1)], &litPtr, litEnd, prefixStart, dictStart, dictEnd); + nuint oneSeqSize = ZSTD_execSequence(op, oend, sequences[seqNb & (8 - 1)], &litPtr, litEnd, prefixStart, dictStart, dictEnd); if ((ERR_isError(oneSeqSize)) != 0) { @@ -2604,7 +2602,7 @@ public static nuint ZSTD_decompressBlock_internal(ZSTD_DCtx_s* dctx, void* dst, return (unchecked((nuint)(-(int)ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall))); } - if (usePrefetchDecoder == 0 && (frame == 0 || (dctx->fParams.windowSize > (uint)((1 << 24)))) && (nbSeq > 4)) + if (usePrefetchDecoder == 0 && (frame == 0 || (dctx->fParams.windowSize > (uint)((1 << 24)))) && (nbSeq > 8)) { uint shareLongOffsets = ZSTD_getLongOffsetsShare(dctx->OFTptr); uint minShare = (uint)(MEM_64bits ? 7 : 20); diff --git a/src/ZstdSharp/Unsafe/ZstdDoubleFast.cs b/src/ZstdSharp/Unsafe/ZstdDoubleFast.cs index acdd216..803dd67 100644 --- a/src/ZstdSharp/Unsafe/ZstdDoubleFast.cs +++ b/src/ZstdSharp/Unsafe/ZstdDoubleFast.cs @@ -449,7 +449,7 @@ private static nuint ZSTD_compressBlock_doubleFast_extDict_generic(ZSTD_matchSta nuint mLength; hashSmall[hSmall] = hashLong[hLong] = curr; - if (((((uint)((prefixStartIndex - 1) - repIndex) >= 3) && (repIndex > dictStartIndex))) && (MEM_read32((void*)repMatch) == MEM_read32((void*)(ip + 1)))) + if (((((uint)((prefixStartIndex - 1) - repIndex) >= 3) && (offset_1 < curr + 1 - dictStartIndex))) && (MEM_read32((void*)repMatch) == MEM_read32((void*)(ip + 1)))) { byte* repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; @@ -548,7 +548,7 @@ private static nuint ZSTD_compressBlock_doubleFast_extDict_generic(ZSTD_matchSta uint repIndex2 = current2 - offset_2; byte* repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : @base + repIndex2; - if (((((uint)((prefixStartIndex - 1) - repIndex2) >= 3) && (repIndex2 > dictStartIndex))) && (MEM_read32((void*)repMatch2) == MEM_read32((void*)ip))) + if (((((uint)((prefixStartIndex - 1) - repIndex2) >= 3) && (offset_2 < current2 - dictStartIndex))) && (MEM_read32((void*)repMatch2) == MEM_read32((void*)ip))) { byte* repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; nuint repLength2 = ZSTD_count_2segments(ip + 4, repMatch2 + 4, iend, repEnd2, prefixStart) + 4; diff --git a/src/ZstdSharp/Unsafe/ZstdFast.cs b/src/ZstdSharp/Unsafe/ZstdFast.cs index 03eb24a..6106bb9 100644 --- a/src/ZstdSharp/Unsafe/ZstdFast.cs +++ b/src/ZstdSharp/Unsafe/ZstdFast.cs @@ -436,8 +436,7 @@ private static nuint ZSTD_compressBlock_fast_extDict_generic(ZSTD_matchState_t* byte* repMatch = repBase + repIndex; hashTable[h] = curr; - assert(offset_1 <= curr + 1); - if (((((uint)((prefixStartIndex - 1) - repIndex) >= 3) && (repIndex > dictStartIndex))) && (MEM_read32((void*)repMatch) == MEM_read32((void*)(ip + 1)))) + if (((((uint)((prefixStartIndex - 1) - repIndex) >= 3) && (offset_1 < curr + 1 - dictStartIndex))) && (MEM_read32((void*)repMatch) == MEM_read32((void*)(ip + 1)))) { byte* repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; nuint rLength = ZSTD_count_2segments(ip + 1 + 4, repMatch + 4, iend, repMatchEnd, prefixStart) + 4; @@ -488,7 +487,7 @@ private static nuint ZSTD_compressBlock_fast_extDict_generic(ZSTD_matchState_t* uint repIndex2 = current2 - offset_2; byte* repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : @base + repIndex2; - if (((((uint)((prefixStartIndex - 1) - repIndex2) >= 3) && (repIndex2 > dictStartIndex))) && (MEM_read32((void*)repMatch2) == MEM_read32((void*)ip))) + if (((((uint)((prefixStartIndex - 1) - repIndex2) >= 3) && (offset_2 < curr - dictStartIndex))) && (MEM_read32((void*)repMatch2) == MEM_read32((void*)ip))) { byte* repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; nuint repLength2 = ZSTD_count_2segments(ip + 4, repMatch2 + 4, iend, repEnd2, prefixStart) + 4; diff --git a/src/ZstdSharp/Unsafe/ZstdInternal.cs b/src/ZstdSharp/Unsafe/ZstdInternal.cs index 8f4264f..be0bf61 100644 --- a/src/ZstdSharp/Unsafe/ZstdInternal.cs +++ b/src/ZstdSharp/Unsafe/ZstdInternal.cs @@ -282,13 +282,11 @@ private static void _force_has_format_string(sbyte* format) /*-******************************************* * Shared functions to include for inlining *********************************************/ - [InlineMethod.Inline] private static void ZSTD_copy8(void* dst, void* src) { memcpy((dst), (src), (8)); } - [InlineMethod.Inline] private static void ZSTD_copy16(void* dst, void* src) { memcpy((dst), (src), (16)); @@ -302,7 +300,6 @@ private static void ZSTD_copy16(void* dst, void* src) * The src buffer must be before the dst buffer. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] private static void ZSTD_wildcopy(void* dst, void* src, nint length, ZSTD_overlap_e ovtype) { nint diff = (nint)((byte*)(dst) - (byte*)(src)); @@ -373,7 +370,7 @@ private static nuint ZSTD_limitCopy(void* dst, nuint dstCapacity, void* src, nui /** * Returns the ZSTD_sequenceLength for the given sequences. It handles the decoding of long sequences - * indicated by longLengthPos and longLengthID, and adds MINMATCH back to matchLength. + * indicated by longLengthPos and longLengthType, and adds MINMATCH back to matchLength. */ [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ZSTD_sequenceLength ZSTD_getSequenceLength(seqStore_t* seqStore, seqDef_s* seq) @@ -384,12 +381,12 @@ private static ZSTD_sequenceLength ZSTD_getSequenceLength(seqStore_t* seqStore, seqLen.matchLength = (uint)(seq->matchLength + 3); if (seqStore->longLengthPos == (uint)(seq - seqStore->sequencesStart)) { - if (seqStore->longLengthID == 1) + if (seqStore->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_literalLength) { seqLen.litLength += 0xFFFF; } - if (seqStore->longLengthID == 2) + if (seqStore->longLengthType == ZSTD_longLengthType_e.ZSTD_llt_matchLength) { seqLen.matchLength += 0xFFFF; } diff --git a/src/ZstdSharp/Unsafe/ZstdLazy.cs b/src/ZstdSharp/Unsafe/ZstdLazy.cs index f19775a..3094c96 100644 --- a/src/ZstdSharp/Unsafe/ZstdLazy.cs +++ b/src/ZstdSharp/Unsafe/ZstdLazy.cs @@ -1,5 +1,13 @@ using System; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +#if NET5_0_OR_GREATER +using System.Runtime.Intrinsics.Arm; +#endif +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics.X86; +#endif using static ZstdSharp.UnsafeHelper; namespace ZstdSharp.Unsafe @@ -9,6 +17,7 @@ public static unsafe partial class Methods /*-************************************* * Binary Tree search ***************************************/ + [InlineMethod.Inline] private static void ZSTD_updateDUBT(ZSTD_matchState_t* ms, byte* ip, byte* iend, uint mls) { ZSTD_compressionParameters* cParams = &ms->cParams; @@ -47,6 +56,7 @@ private static void ZSTD_updateDUBT(ZSTD_matchState_t* ms, byte* ip, byte* iend, * sort one already inserted but unsorted position * assumption : curr >= btlow == (curr - btmask) * doesn't fail */ + [InlineMethod.Inline] private static void ZSTD_insertDUBT1(ZSTD_matchState_t* ms, uint curr, byte* inputEnd, uint nbCompares, uint btLow, ZSTD_dictMode_e dictMode) { ZSTD_compressionParameters* cParams = &ms->cParams; @@ -212,6 +222,7 @@ private static nuint ZSTD_DUBT_findBetterDictMatch(ZSTD_matchState_t* ms, byte* return bestLength; } + [InlineMethod.Inline] private static nuint ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, byte* ip, byte* iend, nuint* offsetPtr, uint mls, ZSTD_dictMode_e dictMode) { ZSTD_compressionParameters* cParams = &ms->cParams; @@ -367,6 +378,7 @@ private static nuint ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, byte* ip, by /** ZSTD_BtFindBestMatch() : Tree updater, providing best match */ [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] private static nuint ZSTD_BtFindBestMatch(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr, uint mls, ZSTD_dictMode_e dictMode) { if (ip < ms->window.@base + ms->nextToUpdate) @@ -380,106 +392,91 @@ private static nuint ZSTD_BtFindBestMatch(ZSTD_matchState_t* ms, byte* ip, byte* private static nuint ZSTD_BtFindBestMatch_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) { + uint mls; switch (ms->cParams.minMatch) { default: case 4: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_noDict); + mls = 4; + break; } case 5: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_noDict); + mls = 5; + break; } case 7: case 6: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_noDict); + mls = 6; + break; } } + return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_noDict); } private static nuint ZSTD_BtFindBestMatch_dictMatchState_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) { + uint mls; switch (ms->cParams.minMatch) { default: case 4: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_dictMatchState); + mls = 4; + break; } case 5: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_dictMatchState); + mls = 5; + break; } case 7: case 6: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_dictMatchState); + mls = 6; + break; } } + return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_dictMatchState); } private static nuint ZSTD_BtFindBestMatch_extDict_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) { + uint mls; switch (ms->cParams.minMatch) { default: case 4: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_extDict); + mls = 4; + break; } case 5: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_extDict); + mls = 5; + break; } case 7: case 6: { - return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_extDict); + mls = 6; + break; } } + return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_extDict); } - /* Update chains up to ip (excluded) - Assumption : always within prefix (i.e. not within extDict) */ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint ZSTD_insertAndFindFirstIndex_internal(ZSTD_matchState_t* ms, ZSTD_compressionParameters* cParams, byte* ip, uint mls) - { - uint* hashTable = ms->hashTable; - uint hashLog = cParams->hashLog; - uint* chainTable = ms->chainTable; - uint chainMask = (uint)((1 << (int)cParams->chainLog) - 1); - byte* @base = ms->window.@base; - uint target = (uint)(ip - @base); - uint idx = ms->nextToUpdate; - - while (idx < target) - { - nuint h = ZSTD_hashPtr((void*)(@base + idx), hashLog, mls); - - chainTable[(idx) & (chainMask)] = hashTable[h]; - hashTable[h] = idx; - idx++; - } - - ms->nextToUpdate = target; - return hashTable[ZSTD_hashPtr((void*)ip, hashLog, mls)]; - } - - public static uint ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, byte* ip) - { - ZSTD_compressionParameters* cParams = &ms->cParams; - - return ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, ms->cParams.minMatch); - } - + /*********************************** + * Dedicated dict search + ***********************************/ public static void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_t* ms, byte* ip) { byte* @base = ms->window.@base; @@ -501,7 +498,7 @@ public static void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_ uint hashIdx; assert(ms->cParams.chainLog <= 24); - assert(ms->cParams.hashLog >= ms->cParams.chainLog); + assert(ms->cParams.hashLog > ms->cParams.chainLog); assert(idx != 0); assert(tmpMinChain <= minChain); for (; idx < target; idx++) @@ -542,7 +539,7 @@ public static void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_ { if (i < minChain) { - if (i == 0 || countBeyondMinChain++ > cacheSize) + if (i == 0 || ++countBeyondMinChain > cacheSize) { break; } @@ -587,33 +584,748 @@ public static void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_ hashTable[bucketIdx + i] = 0; } - hashTable[bucketIdx + bucketSize - 1] = chainPackedPointer; - } + hashTable[bucketIdx + bucketSize - 1] = chainPackedPointer; + } + + for (idx = ms->nextToUpdate; idx < target; idx++) + { + uint h = (uint)(ZSTD_hashPtr((void*)(@base + idx), hashLog, ms->cParams.minMatch)) << 2; + uint i; + + for (i = cacheSize - 1; i != 0; i--) + { + hashTable[h + i] = hashTable[h + i - 1]; + } + + hashTable[h] = idx; + } + + ms->nextToUpdate = target; + } + + /* Returns the longest match length found in the dedicated dict search structure. + * If none are longer than the argument ml, then ml will be returned. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_dedicatedDictSearch_lazy_search(nuint* offsetPtr, nuint ml, uint nbAttempts, ZSTD_matchState_t* dms, byte* ip, byte* iLimit, byte* prefixStart, uint curr, uint dictLimit, nuint ddsIdx) + { + uint ddsLowestIndex = dms->window.dictLimit; + byte* ddsBase = dms->window.@base; + byte* ddsEnd = dms->window.nextSrc; + uint ddsSize = (uint)(ddsEnd - ddsBase); + uint ddsIndexDelta = dictLimit - ddsSize; + uint bucketSize = (uint)((1 << 2)); + uint bucketLimit = nbAttempts < bucketSize - 1 ? nbAttempts : bucketSize - 1; + uint ddsAttempt; + uint matchIndex; + + for (ddsAttempt = 0; ddsAttempt < bucketSize - 1; ddsAttempt++) + { + Prefetch0((void*)(ddsBase + dms->hashTable[ddsIdx + ddsAttempt])); + } + + + { + uint chainPackedPointer = dms->hashTable[ddsIdx + bucketSize - 1]; + uint chainIndex = chainPackedPointer >> 8; + + Prefetch0((void*)(&dms->chainTable[chainIndex])); + } + + for (ddsAttempt = 0; ddsAttempt < bucketLimit; ddsAttempt++) + { + nuint currentMl = 0; + byte* match; + + matchIndex = dms->hashTable[ddsIdx + ddsAttempt]; + match = ddsBase + matchIndex; + if (matchIndex == 0) + { + return ml; + } + + assert(matchIndex >= ddsLowestIndex); + assert(match + 4 <= ddsEnd); + if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + { + currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, ddsEnd, prefixStart) + 4; + } + + if (currentMl > ml) + { + ml = currentMl; + *offsetPtr = curr - (matchIndex + ddsIndexDelta) + (uint)((3 - 1)); + if (ip + currentMl == iLimit) + { + return ml; + } + } + } + + + { + uint chainPackedPointer = dms->hashTable[ddsIdx + bucketSize - 1]; + uint chainIndex = chainPackedPointer >> 8; + uint chainLength = chainPackedPointer & 0xFF; + uint chainAttempts = nbAttempts - ddsAttempt; + uint chainLimit = chainAttempts > chainLength ? chainLength : chainAttempts; + uint chainAttempt; + + for (chainAttempt = 0; chainAttempt < chainLimit; chainAttempt++) + { + Prefetch0((void*)(ddsBase + dms->chainTable[chainIndex + chainAttempt])); + } + + for (chainAttempt = 0; chainAttempt < chainLimit; chainAttempt++ , chainIndex++) + { + nuint currentMl = 0; + byte* match; + + matchIndex = dms->chainTable[chainIndex]; + match = ddsBase + matchIndex; + assert(matchIndex >= ddsLowestIndex); + assert(match + 4 <= ddsEnd); + if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + { + currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, ddsEnd, prefixStart) + 4; + } + + if (currentMl > ml) + { + ml = currentMl; + *offsetPtr = curr - (matchIndex + ddsIndexDelta) + (uint)((3 - 1)); + if (ip + currentMl == iLimit) + { + break; + } + } + } + } + + return ml; + } + + /* Update chains up to ip (excluded) + Assumption : always within prefix (i.e. not within extDict) */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ZSTD_insertAndFindFirstIndex_internal(ZSTD_matchState_t* ms, ZSTD_compressionParameters* cParams, byte* ip, uint mls) + { + uint* hashTable = ms->hashTable; + uint hashLog = cParams->hashLog; + uint* chainTable = ms->chainTable; + uint chainMask = (uint)((1 << (int)cParams->chainLog) - 1); + byte* @base = ms->window.@base; + uint target = (uint)(ip - @base); + uint idx = ms->nextToUpdate; + + while (idx < target) + { + nuint h = ZSTD_hashPtr((void*)(@base + idx), hashLog, mls); + + chainTable[(idx) & (chainMask)] = hashTable[h]; + hashTable[h] = idx; + idx++; + } + + ms->nextToUpdate = target; + return hashTable[ZSTD_hashPtr((void*)ip, hashLog, mls)]; + } + + public static uint ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, byte* ip) + { + ZSTD_compressionParameters* cParams = &ms->cParams; + + return ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, ms->cParams.minMatch); + } + + /* inlining is important to hardwire a hot branch (template emulation) */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_HcFindBestMatch_generic(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr, uint mls, ZSTD_dictMode_e dictMode) + { + ZSTD_compressionParameters* cParams = &ms->cParams; + uint* chainTable = ms->chainTable; + uint chainSize = (uint)((1 << (int)cParams->chainLog)); + uint chainMask = chainSize - 1; + byte* @base = ms->window.@base; + byte* dictBase = ms->window.dictBase; + uint dictLimit = ms->window.dictLimit; + byte* prefixStart = @base + dictLimit; + byte* dictEnd = dictBase + dictLimit; + uint curr = (uint)(ip - @base); + uint maxDistance = 1U << (int)cParams->windowLog; + uint lowestValid = ms->window.lowLimit; + uint withinMaxDistance = (curr - lowestValid > maxDistance) ? curr - maxDistance : lowestValid; + uint isDictionary = (((ms->loadedDictEnd != 0)) ? 1U : 0U); + uint lowLimit = isDictionary != 0 ? lowestValid : withinMaxDistance; + uint minChain = curr > chainSize ? curr - chainSize : 0; + uint nbAttempts = 1U << (int)cParams->searchLog; + nuint ml = (nuint)(4 - 1); + ZSTD_matchState_t* dms = ms->dictMatchState; + uint ddsHashLog = dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch ? dms->cParams.hashLog - 2 : 0; + nuint ddsIdx = dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch ? ZSTD_hashPtr((void*)ip, ddsHashLog, mls) << 2 : 0; + uint matchIndex; + + if (dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch) + { + uint* entry = &dms->hashTable[ddsIdx]; + + Prefetch0((void*)entry); + } + + matchIndex = ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, mls); + for (; ((matchIndex >= lowLimit) && (nbAttempts > 0)); nbAttempts--) + { + nuint currentMl = 0; + + if ((dictMode != ZSTD_dictMode_e.ZSTD_extDict) || matchIndex >= dictLimit) + { + byte* match = @base + matchIndex; + + assert(matchIndex >= dictLimit); + if (match[ml] == ip[ml]) + { + currentMl = ZSTD_count(ip, match, iLimit); + } + } + else + { + byte* match = dictBase + matchIndex; + + assert(match + 4 <= dictEnd); + if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + { + currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, dictEnd, prefixStart) + 4; + } + } + + if (currentMl > ml) + { + ml = currentMl; + *offsetPtr = curr - matchIndex + (uint)((3 - 1)); + if (ip + currentMl == iLimit) + { + break; + } + } + + if (matchIndex <= minChain) + { + break; + } + + matchIndex = chainTable[(matchIndex) & (chainMask)]; + } + + if (dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch) + { + ml = ZSTD_dedicatedDictSearch_lazy_search(offsetPtr, ml, nbAttempts, dms, ip, iLimit, prefixStart, curr, dictLimit, ddsIdx); + } + else if (dictMode == ZSTD_dictMode_e.ZSTD_dictMatchState) + { + uint* dmsChainTable = dms->chainTable; + uint dmsChainSize = (uint)((1 << (int)dms->cParams.chainLog)); + uint dmsChainMask = dmsChainSize - 1; + uint dmsLowestIndex = dms->window.dictLimit; + byte* dmsBase = dms->window.@base; + byte* dmsEnd = dms->window.nextSrc; + uint dmsSize = (uint)(dmsEnd - dmsBase); + uint dmsIndexDelta = dictLimit - dmsSize; + uint dmsMinChain = dmsSize > dmsChainSize ? dmsSize - dmsChainSize : 0; + + matchIndex = dms->hashTable[ZSTD_hashPtr((void*)ip, dms->cParams.hashLog, mls)]; + for (; ((matchIndex >= dmsLowestIndex) && (nbAttempts > 0)); nbAttempts--) + { + nuint currentMl = 0; + byte* match = dmsBase + matchIndex; + + assert(match + 4 <= dmsEnd); + if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + { + currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, dmsEnd, prefixStart) + 4; + } + + if (currentMl > ml) + { + ml = currentMl; + *offsetPtr = curr - (matchIndex + dmsIndexDelta) + (uint)((3 - 1)); + if (ip + currentMl == iLimit) + { + break; + } + } + + if (matchIndex <= dmsMinChain) + { + break; + } + + matchIndex = dmsChainTable[matchIndex & dmsChainMask]; + } + } + + return ml; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_HcFindBestMatch_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + { + uint mls; + switch (ms->cParams.minMatch) + { + default: + case 4: + { + mls = 4; + break; + } + + case 5: + { + mls = 5; + break; + } + + case 7: + case 6: + { + mls = 6; + break; + } + } + return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_noDict); + } + + private static nuint ZSTD_HcFindBestMatch_dictMatchState_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + { + uint mls; + switch (ms->cParams.minMatch) + { + default: + case 4: + { + mls = 4; + break; + } + + case 5: + { + mls = 5; + break; + } + + case 7: + case 6: + { + mls = 6; + break; + } + } + return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_dictMatchState); + } + + private static nuint ZSTD_HcFindBestMatch_dedicatedDictSearch_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + { + uint mls; + switch (ms->cParams.minMatch) + { + default: + case 4: + { + mls = 4; + break; + } + + case 5: + { + mls = 5; + break; + } + + case 7: + case 6: + { + mls = 6; + break; + } + } + return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_HcFindBestMatch_extDict_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + { + uint mls; + switch (ms->cParams.minMatch) + { + default: + case 4: + { + mls = 4; + break; + } + + case 5: + { + mls = 5; + break; + } + + case 7: + case 6: + { + mls = 6; + break; + } + } + return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, mls, ZSTD_dictMode_e.ZSTD_extDict); + } + + /* Returns a 128-bit container with 128-bits from src */ + [InlineMethod.Inline] + private static Vector128 ZSTD_Vec128_read(void* src) + { +#if NETCOREAPP3_0_OR_GREATER + if (Sse2.IsSupported) + { + return Sse2.LoadVector128((byte*) src); + } +#endif +#if NET5_0_OR_GREATER + if (AdvSimd.IsSupported) + { + return AdvSimd.LoadVector128((byte*) src); + } +#endif + Vector128 ret; + memcpy(&ret, src, sizeof(Vector128)); + return ret; + } + + /* Returns a ZSTD_Vec128 with the byte "val" packed 16 times */ + [InlineMethod.Inline] + private static Vector128 ZSTD_Vec128_set8(byte val) + { + return Vector128.Create(val); + } + +#if NET5_0_OR_GREATER + /* Mimics '_mm_movemask_epi8()' from SSE */ + static uint ZSTD_vmovmaskq_u8(Vector128 val) + { + /* Shift out everything but the MSB bits in each byte */ + Vector128 highBits = AdvSimd.ShiftRightLogical(val, 7).As(); + /* Merge the even lanes together with vsra (right shift and add) */ + Vector128 paired16 = AdvSimd.ShiftRightLogicalAdd(highBits, highBits, 7).As(); + Vector128 paired32 = AdvSimd.ShiftRightLogicalAdd(paired16, paired16, 14).As(); + Vector128 paired64 = AdvSimd.ShiftRightLogicalAdd(paired32, paired32, 28).As(); + /* Extract the low 8 bits from each lane, merge */ + return AdvSimd.Extract(paired64, 0) | ((uint) AdvSimd.Extract(paired64, 8) << 8); + } +#endif + + /* Do byte-by-byte comparison result of x and y. Then collapse 128-bit resultant mask + * into a 32-bit mask that is the MSB of each byte. + * */ + [InlineMethod.Inline] + private static uint ZSTD_Vec128_cmpMask8(Vector128 x, Vector128 y) + { +#if NETCOREAPP3_0_OR_GREATER + if (Sse2.IsSupported) + { + return (uint) Sse2.MoveMask(Sse2.CompareEqual(x, y)); + } +#endif +#if NET5_0_OR_GREATER + if (AdvSimd.IsSupported) + { + return ZSTD_vmovmaskq_u8(AdvSimd.CompareEqual(x, y)); + } +#endif + uint res = 0; + int i = 0; + int l = 0; + for (; i < Vector128.Count; ++i) + { + nuint cmp1 = x.As().GetElement(i); + nuint cmp2 = y.As().GetElement(i); + int j = 0; + for (; j < sizeof(nuint); ++j, ++l) + { + if (((cmp1 >> j * 8) & 0xFF) == ((cmp2 >> j * 8) & 0xFF)) + { + res |= ((uint)1 << (j + i * sizeof(nuint))); + } + } + } + return res; + } + + [InlineMethod.Inline] + private static ZSTD_Vec256 ZSTD_Vec256_read(void* ptr) + { + ZSTD_Vec256 v; + + v.fst = ZSTD_Vec128_read(ptr); + v.snd = ZSTD_Vec128_read((Vector128*) ptr + 1); + return v; + } + + [InlineMethod.Inline] + private static ZSTD_Vec256 ZSTD_Vec256_set8(byte val) + { + ZSTD_Vec256 v; + + v.fst = ZSTD_Vec128_set8(val); + v.snd = ZSTD_Vec128_set8(val); + return v; + } + + [InlineMethod.Inline] + private static uint ZSTD_Vec256_cmpMask8(ZSTD_Vec256 x, ZSTD_Vec256 y) + { + uint fstMask; + uint sndMask; + + fstMask = ZSTD_Vec128_cmpMask8(x.fst, y.fst); + sndMask = ZSTD_Vec128_cmpMask8(x.snd, y.snd); + return fstMask | (sndMask << 16); + } + + /* ZSTD_VecMask_next(): + * Starting from the LSB, returns the idx of the next non-zero bit. + * Basically counting the nb of trailing zeroes. + */ + [InlineMethod.Inline] + private static uint ZSTD_VecMask_next(uint val) + { + if (val == 0) + { + return 0; + } + + return (uint)BitOperations.TrailingZeroCount(val); + } + + /* ZSTD_VecMask_rotateRight(): + * Rotates a bitfield to the right by "rotation" bits. + * If the rotation is greater than totalBits, the returned mask is 0. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + private static uint ZSTD_VecMask_rotateRight(uint mask, uint rotation, uint totalBits) + { + if (rotation == 0) + { + return mask; + } + + switch (totalBits) + { + default: + { + assert(0 != 0); + } + + + goto case 16; + case 16: + { + return (mask >> (int)rotation) | (ushort)(mask << (int)(16 - rotation)); + } + + case 32: + { + return (mask >> (int)rotation) | (uint)(mask << (int)(32 - rotation)); + } + } + } + + /* ZSTD_row_nextIndex(): + * Returns the next index to insert at within a tagTable row, and updates the "head" + * value to reflect the update. Essentially cycles backwards from [0, {entries per row}) + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + private static uint ZSTD_row_nextIndex(byte* tagRow, uint rowMask) + { + uint next = (uint)((*tagRow - 1)) & rowMask; + + *tagRow = (byte)(next); + return next; + } + + /* ZSTD_isAligned(): + * Checks that a pointer is aligned to "align" bytes which must be a power of 2. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ZSTD_isAligned(void* ptr, nuint align) + { + assert((align & (align - 1)) == 0); + return (((((nuint)(ptr)) & (align - 1)) == 0) ? 1 : 0); + } + + /* ZSTD_row_prefetch(): + * Performs prefetching for the hashTable and tagTable at a given row. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + private static void ZSTD_row_prefetch(uint* hashTable, ushort* tagTable, uint relRow, uint rowLog) + { + Prefetch0((void*)(hashTable + relRow)); + if (rowLog == 5) + { + Prefetch0((void*)(hashTable + relRow + 16)); + } + + Prefetch0((void*)(tagTable + relRow)); + assert(rowLog == 4 || rowLog == 5); + assert((ZSTD_isAligned((void*)(hashTable + relRow), 64)) != 0); + assert((ZSTD_isAligned((void*)(tagTable + relRow), (nuint)(1) << (int)rowLog)) != 0); + } + + /* ZSTD_row_fillHashCache(): + * Fill up the hash cache starting at idx, prefetching up to ZSTD_ROW_HASH_CACHE_SIZE entries, + * but not beyond iLimit. + */ + private static void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, byte* @base, uint rowLog, uint mls, uint idx, byte* iLimit) + { + uint* hashTable = ms->hashTable; + ushort* tagTable = ms->tagTable; + uint hashLog = ms->rowHashLog; + uint maxElemsToPrefetch = (uint)((@base + idx) > iLimit ? 0 : (uint)(iLimit - (@base + idx) + 1)); + uint lim = idx + (uint)((8) < (maxElemsToPrefetch) ? (8) : (maxElemsToPrefetch)); + + for (; idx < lim; ++idx) + { + uint hash = (uint)(ZSTD_hashPtr((void*)(@base + idx), hashLog + 8, mls)); + uint row = (hash >> 8) << (int)rowLog; + + ZSTD_row_prefetch(hashTable, tagTable, row, rowLog); + ms->hashCache[idx & (uint)((8 - 1))] = hash; + } + + } + + /* ZSTD_row_nextCachedHash(): + * Returns the hash of base + idx, and replaces the hash in the hash cache with the byte at + * base + idx + ZSTD_ROW_HASH_CACHE_SIZE. Also prefetches the appropriate rows from hashTable and tagTable. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + private static uint ZSTD_row_nextCachedHash(uint* cache, uint* hashTable, ushort* tagTable, byte* @base, uint idx, uint hashLog, uint rowLog, uint mls) + { + uint newHash = (uint)(ZSTD_hashPtr((void*)(@base + idx + 8), hashLog + 8, mls)); + uint row = (newHash >> 8) << (int)rowLog; + + ZSTD_row_prefetch(hashTable, tagTable, row, rowLog); + + { + uint hash = cache[idx & (uint)((8 - 1))]; + + cache[idx & (uint)((8 - 1))] = newHash; + return hash; + } + } + + /* ZSTD_row_update_internal(): + * Inserts the byte at ip into the appropriate position in the hash table. + * Determines the relative row, and the position within the {16, 32} entry row to insert at. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + private static void ZSTD_row_update_internal(ZSTD_matchState_t* ms, byte* ip, uint mls, uint rowLog, uint rowMask, uint useCache) + { + uint* hashTable = ms->hashTable; + ushort* tagTable = ms->tagTable; + uint hashLog = ms->rowHashLog; + byte* @base = ms->window.@base; + uint target = (uint)(ip - @base); + uint idx = ms->nextToUpdate; + + for (; idx < target; ++idx) + { + uint hash = useCache != 0 ? ZSTD_row_nextCachedHash((uint*)ms->hashCache, hashTable, tagTable, @base, idx, hashLog, rowLog, mls) : (uint)(ZSTD_hashPtr((void*)(@base + idx), hashLog + 8, mls)); + uint relRow = (hash >> 8) << (int)rowLog; + uint* row = hashTable + relRow; + byte* tagRow = (byte*)(tagTable + relRow); + uint pos = ZSTD_row_nextIndex(tagRow, rowMask); + + assert(hash == ZSTD_hashPtr((void*)(@base + idx), hashLog + 8, mls)); + ((byte*)(tagRow))[pos + 1] = (byte)(hash & ((1U << 8) - 1)); + row[pos] = idx; + } + + ms->nextToUpdate = target; + } + + /* ZSTD_row_update(): + * External wrapper for ZSTD_row_update_internal(). Used for filling the hashtable during dictionary + * processing. + */ + public static void ZSTD_row_update(ZSTD_matchState_t* ms, byte* ip) + { + uint rowLog = (uint)(ms->cParams.searchLog < 5 ? 4 : 5); + uint rowMask = (1U << (int)rowLog) - 1; + uint mls = ((ms->cParams.minMatch) < (6) ? (ms->cParams.minMatch) : (6)); + + ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 0); + } + + /* Returns a ZSTD_VecMask (U32) that has the nth bit set to 1 if the newly-computed "tag" matches + * the hash at the nth position in a row of the tagTable. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [InlineMethod.Inline] + private static uint ZSTD_row_getMatchMask(byte* tagRow, byte tag, uint head, uint rowEntries) + { + uint matches = 0; - for (idx = ms->nextToUpdate; idx < target; idx++) + if (rowEntries == 16) { - uint h = (uint)(ZSTD_hashPtr((void*)(@base + idx), hashLog, ms->cParams.minMatch)) << 2; - uint i; + var hashes = ZSTD_Vec128_read((void*)(tagRow + 1)); + var expandedTags = ZSTD_Vec128_set8(tag); - for (i = cacheSize - 1; i != 0; i--) - { - hashTable[h + i] = hashTable[h + i - 1]; - } + matches = ZSTD_Vec128_cmpMask8(hashes, expandedTags); + } + else if (rowEntries == 32) + { + ZSTD_Vec256 hashes = ZSTD_Vec256_read((void*)(tagRow + 1)); + ZSTD_Vec256 expandedTags = ZSTD_Vec256_set8(tag); - hashTable[h] = idx; + matches = ZSTD_Vec256_cmpMask8(hashes, expandedTags); + } + else + { + assert(0 != 0); } - ms->nextToUpdate = target; + return ZSTD_VecMask_rotateRight(matches, head, rowEntries); } - /* inlining is important to hardwire a hot branch (template emulation) */ + /* The high-level approach of the SIMD row based match finder is as follows: + * - Figure out where to insert the new entry: + * - Generate a hash from a byte along with an additional 1-byte "short hash". The additional byte is our "tag" + * - The hashTable is effectively split into groups or "rows" of 16 or 32 entries of U32, and the hash determines + * which row to insert into. + * - Determine the correct position within the row to insert the entry into. Each row of 16 or 32 can + * be considered as a circular buffer with a "head" index that resides in the tagTable. + * - Also insert the "tag" into the equivalent row and position in the tagTable. + * - Note: The tagTable has 17 or 33 1-byte entries per row, due to 16 or 32 tags, and 1 "head" entry. + * The 17 or 33 entry rows are spaced out to occur every 32 or 64 bytes, respectively, + * for alignment/performance reasons, leaving some bytes unused. + * - Use SIMD to efficiently compare the tags in the tagTable to the 1-byte "short hash" and + * generate a bitfield that we can cycle through to check the collisions in the hash table. + * - Pick the longest match. + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nuint ZSTD_HcFindBestMatch_generic(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr, uint mls, ZSTD_dictMode_e dictMode) + [InlineMethod.Inline] + private static nuint ZSTD_RowFindBestMatch_generic(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr, uint mls, ZSTD_dictMode_e dictMode, uint rowLog) { + uint* hashTable = ms->hashTable; + ushort* tagTable = ms->tagTable; + uint* hashCache = (uint*)ms->hashCache; + uint hashLog = ms->rowHashLog; ZSTD_compressionParameters* cParams = &ms->cParams; - uint* chainTable = ms->chainTable; - uint chainSize = (uint)((1 << (int)cParams->chainLog)); - uint chainMask = chainSize - 1; byte* @base = ms->window.@base; byte* dictBase = ms->window.dictBase; uint dictLimit = ms->window.dictLimit; @@ -625,297 +1337,258 @@ private static nuint ZSTD_HcFindBestMatch_generic(ZSTD_matchState_t* ms, byte* i uint withinMaxDistance = (curr - lowestValid > maxDistance) ? curr - maxDistance : lowestValid; uint isDictionary = (((ms->loadedDictEnd != 0)) ? 1U : 0U); uint lowLimit = isDictionary != 0 ? lowestValid : withinMaxDistance; - uint minChain = curr > chainSize ? curr - chainSize : 0; - uint nbAttempts = 1U << (int)cParams->searchLog; + uint rowEntries = (1U << (int)rowLog); + uint rowMask = rowEntries - 1; + uint cappedSearchLog = ((cParams->searchLog) < (rowLog) ? (cParams->searchLog) : (rowLog)); + uint nbAttempts = 1U << (int)cappedSearchLog; nuint ml = (nuint)(4 - 1); ZSTD_matchState_t* dms = ms->dictMatchState; - uint ddsHashLog = dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch ? dms->cParams.hashLog - 2 : 0; - nuint ddsIdx = dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch ? ZSTD_hashPtr((void*)ip, ddsHashLog, mls) << 2 : 0; - uint matchIndex; + nuint ddsIdx = default; + uint ddsExtraAttempts = default; + uint dmsTag = default; + uint* dmsRow = default; + byte* dmsTagRow = default; if (dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch) { - uint* entry = &dms->hashTable[ddsIdx]; + uint ddsHashLog = dms->cParams.hashLog - 2; - Prefetch0((void*)entry); + + { + ddsIdx = ZSTD_hashPtr((void*)ip, ddsHashLog, mls) << 2; + Prefetch0((void*)(&dms->hashTable[ddsIdx])); + } + + ddsExtraAttempts = cParams->searchLog > rowLog ? 1U << (int)(cParams->searchLog - rowLog) : 0; } - matchIndex = ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, mls); - for (; ((matchIndex >= lowLimit) && (nbAttempts > 0)); nbAttempts--) + if (dictMode == ZSTD_dictMode_e.ZSTD_dictMatchState) { - nuint currentMl = 0; + uint* dmsHashTable = dms->hashTable; + ushort* dmsTagTable = dms->tagTable; + uint dmsHash = (uint)(ZSTD_hashPtr((void*)ip, dms->rowHashLog + 8, mls)); + uint dmsRelRow = (dmsHash >> 8) << (int)rowLog; + + dmsTag = dmsHash & ((1U << 8) - 1); + dmsTagRow = (byte*)(dmsTagTable + dmsRelRow); + dmsRow = dmsHashTable + dmsRelRow; + ZSTD_row_prefetch(dmsHashTable, dmsTagTable, dmsRelRow, rowLog); + } - if ((dictMode != ZSTD_dictMode_e.ZSTD_extDict) || matchIndex >= dictLimit) + ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1); + + { + uint hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, @base, curr, hashLog, rowLog, mls); + uint relRow = (hash >> 8) << (int)rowLog; + uint tag = hash & ((1U << 8) - 1); + uint* row = hashTable + relRow; + byte* tagRow = (byte*)(tagTable + relRow); + uint head = *tagRow & rowMask; + uint* matchBuffer = stackalloc uint[32]; + nuint numMatches = 0; + nuint currMatch = 0; + uint matches = ZSTD_row_getMatchMask(tagRow, (byte)(tag), head, rowEntries); + + for (; (matches > 0) && (nbAttempts > 0); --nbAttempts , matches &= (matches - 1)) { - byte* match = @base + matchIndex; + uint matchPos = (head + ZSTD_VecMask_next(matches)) & rowMask; + uint matchIndex = row[matchPos]; - assert(matchIndex >= dictLimit); - if (match[ml] == ip[ml]) + assert(numMatches < rowEntries); + if (matchIndex < lowLimit) { - currentMl = ZSTD_count(ip, match, iLimit); + break; } - } - else - { - byte* match = dictBase + matchIndex; - assert(match + 4 <= dictEnd); - if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + if ((dictMode != ZSTD_dictMode_e.ZSTD_extDict) || matchIndex >= dictLimit) { - currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, dictEnd, prefixStart) + 4; + Prefetch0((void*)(@base + matchIndex)); } - } - - if (currentMl > ml) - { - ml = currentMl; - *offsetPtr = curr - matchIndex + (uint)((3 - 1)); - if (ip + currentMl == iLimit) + else { - break; + Prefetch0((void*)(dictBase + matchIndex)); } - } - - if (matchIndex <= minChain) - { - break; - } - - matchIndex = chainTable[(matchIndex) & (chainMask)]; - } - - if (dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch) - { - uint ddsLowestIndex = dms->window.dictLimit; - byte* ddsBase = dms->window.@base; - byte* ddsEnd = dms->window.nextSrc; - uint ddsSize = (uint)(ddsEnd - ddsBase); - uint ddsIndexDelta = dictLimit - ddsSize; - uint bucketSize = (uint)((1 << 2)); - uint bucketLimit = nbAttempts < bucketSize - 1 ? nbAttempts : bucketSize - 1; - uint ddsAttempt; - for (ddsAttempt = 0; ddsAttempt < bucketSize - 1; ddsAttempt++) - { - Prefetch0((void*)(ddsBase + dms->hashTable[ddsIdx + ddsAttempt])); + matchBuffer[numMatches++] = matchIndex; } { - uint chainPackedPointer = dms->hashTable[ddsIdx + bucketSize - 1]; - uint chainIndex = chainPackedPointer >> 8; + uint pos = ZSTD_row_nextIndex(tagRow, rowMask); - Prefetch0((void*)(&dms->chainTable[chainIndex])); + tagRow[pos + 1] = (byte)(tag); + row[pos] = ms->nextToUpdate++; } - for (ddsAttempt = 0; ddsAttempt < bucketLimit; ddsAttempt++) + for (; currMatch < numMatches; ++currMatch) { + uint matchIndex = matchBuffer[currMatch]; nuint currentMl = 0; - byte* match; - - matchIndex = dms->hashTable[ddsIdx + ddsAttempt]; - match = ddsBase + matchIndex; - if (matchIndex == 0) - { - return ml; - } - assert(matchIndex >= ddsLowestIndex); - assert(match + 4 <= ddsEnd); - if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + assert(matchIndex < curr); + assert(matchIndex >= lowLimit); + if ((dictMode != ZSTD_dictMode_e.ZSTD_extDict) || matchIndex >= dictLimit) { - currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, ddsEnd, prefixStart) + 4; - } + byte* match = @base + matchIndex; - if (currentMl > ml) - { - ml = currentMl; - *offsetPtr = curr - (matchIndex + ddsIndexDelta) + (uint)((3 - 1)); - if (ip + currentMl == iLimit) + assert(matchIndex >= dictLimit); + if (match[ml] == ip[ml]) { - return ml; + currentMl = ZSTD_count(ip, match, iLimit); } } - } - - - { - uint chainPackedPointer = dms->hashTable[ddsIdx + bucketSize - 1]; - uint chainIndex = chainPackedPointer >> 8; - uint chainLength = chainPackedPointer & 0xFF; - uint chainAttempts = nbAttempts - ddsAttempt; - uint chainLimit = chainAttempts > chainLength ? chainLength : chainAttempts; - uint chainAttempt; - - for (chainAttempt = 0; chainAttempt < chainLimit; chainAttempt++) - { - Prefetch0((void*)(ddsBase + dms->chainTable[chainIndex + chainAttempt])); - } - - for (chainAttempt = 0; chainAttempt < chainLimit; chainAttempt++ , chainIndex++) + else { - nuint currentMl = 0; - byte* match; + byte* match = dictBase + matchIndex; - matchIndex = dms->chainTable[chainIndex]; - match = ddsBase + matchIndex; - assert(matchIndex >= ddsLowestIndex); - assert(match + 4 <= ddsEnd); + assert(match + 4 <= dictEnd); if (MEM_read32((void*)match) == MEM_read32((void*)ip)) { - currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, ddsEnd, prefixStart) + 4; + currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, dictEnd, prefixStart) + 4; } + } - if (currentMl > ml) + if (currentMl > ml) + { + ml = currentMl; + *offsetPtr = curr - matchIndex + (uint)((3 - 1)); + if (ip + currentMl == iLimit) { - ml = currentMl; - *offsetPtr = curr - (matchIndex + ddsIndexDelta) + (uint)((3 - 1)); - if (ip + currentMl == iLimit) - { - break; - } + break; } } } } + + if (dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch) + { + ml = ZSTD_dedicatedDictSearch_lazy_search(offsetPtr, ml, nbAttempts + ddsExtraAttempts, dms, ip, iLimit, prefixStart, curr, dictLimit, ddsIdx); + } else if (dictMode == ZSTD_dictMode_e.ZSTD_dictMatchState) { - uint* dmsChainTable = dms->chainTable; - uint dmsChainSize = (uint)((1 << (int)dms->cParams.chainLog)); - uint dmsChainMask = dmsChainSize - 1; uint dmsLowestIndex = dms->window.dictLimit; byte* dmsBase = dms->window.@base; byte* dmsEnd = dms->window.nextSrc; uint dmsSize = (uint)(dmsEnd - dmsBase); uint dmsIndexDelta = dictLimit - dmsSize; - uint dmsMinChain = dmsSize > dmsChainSize ? dmsSize - dmsChainSize : 0; - matchIndex = dms->hashTable[ZSTD_hashPtr((void*)ip, dms->cParams.hashLog, mls)]; - for (; ((matchIndex >= dmsLowestIndex) && (nbAttempts > 0)); nbAttempts--) + { - nuint currentMl = 0; - byte* match = dmsBase + matchIndex; + uint head = *dmsTagRow & rowMask; + uint* matchBuffer = stackalloc uint[32]; + nuint numMatches = 0; + nuint currMatch = 0; + uint matches = ZSTD_row_getMatchMask(dmsTagRow, (byte)(dmsTag), head, rowEntries); - assert(match + 4 <= dmsEnd); - if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + for (; (matches > 0) && (nbAttempts > 0); --nbAttempts , matches &= (matches - 1)) { - currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, dmsEnd, prefixStart) + 4; - } + uint matchPos = (head + ZSTD_VecMask_next(matches)) & rowMask; + uint matchIndex = dmsRow[matchPos]; - if (currentMl > ml) - { - ml = currentMl; - *offsetPtr = curr - (matchIndex + dmsIndexDelta) + (uint)((3 - 1)); - if (ip + currentMl == iLimit) + if (matchIndex < dmsLowestIndex) { break; } + + Prefetch0((void*)(dmsBase + matchIndex)); + matchBuffer[numMatches++] = matchIndex; } - if (matchIndex <= dmsMinChain) + for (; currMatch < numMatches; ++currMatch) { - break; - } + uint matchIndex = matchBuffer[currMatch]; + nuint currentMl = 0; - matchIndex = dmsChainTable[matchIndex & dmsChainMask]; + assert(matchIndex >= dmsLowestIndex); + assert(matchIndex < curr); + + { + byte* match = dmsBase + matchIndex; + + assert(match + 4 <= dmsEnd); + if (MEM_read32((void*)match) == MEM_read32((void*)ip)) + { + currentMl = ZSTD_count_2segments(ip + 4, match + 4, iLimit, dmsEnd, prefixStart) + 4; + } + } + + if (currentMl > ml) + { + ml = currentMl; + *offsetPtr = curr - (matchIndex + dmsIndexDelta) + (uint)((3 - 1)); + if (ip + currentMl == iLimit) + { + break; + } + } + } } } return ml; } + /* Inlining is important to hardwire a hot branch (template emulation) */ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nuint ZSTD_HcFindBestMatch_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + [InlineMethod.Inline] + private static nuint ZSTD_RowFindBestMatch_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, ZSTD_dictMode_e dictMode, nuint* offsetPtr, uint rowLog) { + uint mls; switch (ms->cParams.minMatch) { default: case 4: { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_noDict); + mls = 4; + break; } case 5: { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_noDict); + mls = 5; + break; } case 7: case 6: { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_noDict); + mls = 6; + break; } } + return ZSTD_RowFindBestMatch_generic(ms, ip, iLimit, offsetPtr, mls, dictMode, rowLog); } - private static nuint ZSTD_HcFindBestMatch_dictMatchState_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_RowFindBestMatch_selectRowLog(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) { - switch (ms->cParams.minMatch) - { - default: - case 4: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_dictMatchState); - } - - case 5: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_dictMatchState); - } + uint cappedSearchLog = ((ms->cParams.searchLog) < (5) ? (ms->cParams.searchLog) : (5)); - case 7: - case 6: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_dictMatchState); - } - } + return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dictMode_e.ZSTD_noDict, offsetPtr, cappedSearchLog == 5U ? 5U : 4U); } - private static nuint ZSTD_HcFindBestMatch_dedicatedDictSearch_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_RowFindBestMatch_dictMatchState_selectRowLog(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) { - switch (ms->cParams.minMatch) - { - default: - case 4: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); - } + uint cappedSearchLog = ((ms->cParams.searchLog) < (5) ? (ms->cParams.searchLog) : (5)); + + return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dictMode_e.ZSTD_dictMatchState, offsetPtr, cappedSearchLog == 5U ? 5U : 4U); + } - case 5: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint ZSTD_RowFindBestMatch_dedicatedDictSearch_selectRowLog(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + { + uint cappedSearchLog = ((ms->cParams.searchLog) < (5) ? (ms->cParams.searchLog) : (5)); - case 7: - case 6: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); - } - } + return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch, offsetPtr, cappedSearchLog == 5U ? 5U : 4U); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nuint ZSTD_HcFindBestMatch_extDict_selectMLS(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) + private static nuint ZSTD_RowFindBestMatch_extDict_selectRowLog(ZSTD_matchState_t* ms, byte* ip, byte* iLimit, nuint* offsetPtr) { - switch (ms->cParams.minMatch) - { - default: - case 4: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMode_e.ZSTD_extDict); - } - - case 5: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMode_e.ZSTD_extDict); - } + uint cappedSearchLog = ((ms->cParams.searchLog) < (5) ? (ms->cParams.searchLog) : (5)); - case 7: - case 6: - { - return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMode_e.ZSTD_extDict); - } - } + return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dictMode_e.ZSTD_extDict, offsetPtr, cappedSearchLog == 5U ? 5U : 4U); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -925,13 +1598,14 @@ private static nuint ZSTD_compressBlock_lazy_generic(ZSTD_matchState_t* ms, seqS byte* ip = istart; byte* anchor = istart; byte* iend = istart + srcSize; - byte* ilimit = iend - 8; + byte* ilimit = searchMethod == searchMethod_e.search_rowHash ? iend - 8 - 8 : iend - 8; byte* @base = ms->window.@base; uint prefixLowestIndex = ms->window.dictLimit; byte* prefixLowest = @base + prefixLowestIndex; + uint rowLog = (uint)(ms->cParams.searchLog < 5 ? 4 : 5); ; - searchMax_f searchMax = searchFuncs[(int)dictMode][((searchMethod == searchMethod_e.search_binaryTree) ? 1 : 0)]; + searchMax_f searchMax = searchFuncs[(int)dictMode][(int)(searchMethod)]; uint offset_1 = rep[0], offset_2 = rep[1], savedOffset = 0; int isDMS = ((dictMode == ZSTD_dictMode_e.ZSTD_dictMatchState) ? 1 : 0); int isDDS = ((dictMode == ZSTD_dictMode_e.ZSTD_dedicatedDictSearch) ? 1 : 0); @@ -969,6 +1643,11 @@ private static nuint ZSTD_compressBlock_lazy_generic(ZSTD_matchState_t* ms, seqS assert(offset_2 <= dictAndPrefixLength); } + if (searchMethod == searchMethod_e.search_rowHash) + { + ZSTD_row_fillHashCache(ms, @base, rowLog, ((ms->cParams.minMatch) < (6) ? (ms->cParams.minMatch) : (6)), ms->nextToUpdate, ilimit); + } + while (ip < ilimit) { nuint matchLength = 0; @@ -1261,6 +1940,52 @@ public static nuint ZSTD_compressBlock_greedy_dedicatedDictSearch(ZSTD_matchStat return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_hashChain, 0, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); } + /* Row-based matchfinder */ + public static nuint ZSTD_compressBlock_lazy2_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 2, ZSTD_dictMode_e.ZSTD_noDict); + } + + public static nuint ZSTD_compressBlock_lazy_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 1, ZSTD_dictMode_e.ZSTD_noDict); + } + + public static nuint ZSTD_compressBlock_greedy_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 0, ZSTD_dictMode_e.ZSTD_noDict); + } + + public static nuint ZSTD_compressBlock_lazy2_dictMatchState_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 2, ZSTD_dictMode_e.ZSTD_dictMatchState); + } + + public static nuint ZSTD_compressBlock_lazy_dictMatchState_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 1, ZSTD_dictMode_e.ZSTD_dictMatchState); + } + + public static nuint ZSTD_compressBlock_greedy_dictMatchState_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 0, ZSTD_dictMode_e.ZSTD_dictMatchState); + } + + public static nuint ZSTD_compressBlock_lazy2_dedicatedDictSearch_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 2, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); + } + + public static nuint ZSTD_compressBlock_lazy_dedicatedDictSearch_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 1, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); + } + + public static nuint ZSTD_compressBlock_greedy_dedicatedDictSearch_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 0, ZSTD_dictMode_e.ZSTD_dedicatedDictSearch); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize, searchMethod_e searchMethod, uint depth) { @@ -1268,7 +1993,7 @@ private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* byte* ip = istart; byte* anchor = istart; byte* iend = istart + srcSize; - byte* ilimit = iend - 8; + byte* ilimit = searchMethod == searchMethod_e.search_rowHash ? iend - 8 - 8 : iend - 8; byte* @base = ms->window.@base; uint dictLimit = ms->window.dictLimit; byte* prefixStart = @base + dictLimit; @@ -1276,11 +2001,18 @@ private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* byte* dictEnd = dictBase + dictLimit; byte* dictStart = dictBase + ms->window.lowLimit; uint windowLog = ms->cParams.windowLog; + uint rowLog = (uint)(ms->cParams.searchLog < 5 ? 4 : 5); ; - searchMax_f searchMax = (searchMax_f)(searchMethod == searchMethod_e.search_binaryTree ? ZSTD_BtFindBestMatch_extDict_selectMLS : ZSTD_HcFindBestMatch_extDict_selectMLS); + + searchMax_f searchMax = searchFuncsExtGeneric[(int)(searchMethod)]; uint offset_1 = rep[0], offset_2 = rep[1]; ip += ((ip == prefixStart) ? 1 : 0); + if (searchMethod == searchMethod_e.search_rowHash) + { + ZSTD_row_fillHashCache(ms, @base, rowLog, ((ms->cParams.minMatch) < (6) ? (ms->cParams.minMatch) : (6)), ms->nextToUpdate, ilimit); + } + while (ip < ilimit) { nuint matchLength = 0; @@ -1295,7 +2027,7 @@ private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* byte* repBase = repIndex < dictLimit ? dictBase : @base; byte* repMatch = repBase + repIndex; - if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (repIndex > windowLow))) + if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (offset_1 < curr + 1 - windowLow))) { if (MEM_read32((void*)(ip + 1)) == MEM_read32((void*)repMatch)) { @@ -1340,7 +2072,7 @@ private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* byte* repBase = repIndex < dictLimit ? dictBase : @base; byte* repMatch = repBase + repIndex; - if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (repIndex > windowLow))) + if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (offset_1 < curr - windowLow))) { if (MEM_read32((void*)ip) == MEM_read32((void*)repMatch)) { @@ -1382,7 +2114,7 @@ private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* byte* repBase = repIndex < dictLimit ? dictBase : @base; byte* repMatch = repBase + repIndex; - if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (repIndex > windowLow))) + if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (offset_1 < curr - windowLow))) { if (MEM_read32((void*)ip) == MEM_read32((void*)repMatch)) { @@ -1451,7 +2183,7 @@ private static nuint ZSTD_compressBlock_lazy_extDict_generic(ZSTD_matchState_t* byte* repBase = repIndex < dictLimit ? dictBase : @base; byte* repMatch = repBase + repIndex; - if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (repIndex > windowLow))) + if ((((uint)((dictLimit - 1) - repIndex) >= 3) && (offset_2 < repCurrent - windowLow))) { if (MEM_read32((void*)ip) == MEM_read32((void*)repMatch)) { @@ -1496,5 +2228,20 @@ public static nuint ZSTD_compressBlock_btlazy2_extDict(ZSTD_matchState_t* ms, se { return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_binaryTree, 2); } + + public static nuint ZSTD_compressBlock_greedy_extDict_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 0); + } + + public static nuint ZSTD_compressBlock_lazy_extDict_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 1); + } + + public static nuint ZSTD_compressBlock_lazy2_extDict_row(ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + { + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, searchMethod_e.search_rowHash, 2); + } } } diff --git a/src/ZstdSharp/Unsafe/ZstdLdm.cs b/src/ZstdSharp/Unsafe/ZstdLdm.cs index b305f28..6f7116e 100644 --- a/src/ZstdSharp/Unsafe/ZstdLdm.cs +++ b/src/ZstdSharp/Unsafe/ZstdLdm.cs @@ -25,6 +25,53 @@ private static void ZSTD_ldm_gear_init(ldmRollingHashState_t* state, ldmParams_t } } + /** ZSTD_ldm_gear_reset() + * Feeds [data, data + minMatchLength) into the hash without registering any + * splits. This effectively resets the hash state. This is used when skipping + * over data, either at the beginning of a block, or skipping sections. + */ + private static void ZSTD_ldm_gear_reset(ldmRollingHashState_t* state, byte* data, nuint minMatchLength) + { + ulong hash = state->rolling; + nuint n = 0; + + while (n + 3 < minMatchLength) + { + + { + hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; + n += 1; + } + + + { + hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; + n += 1; + } + + + { + hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; + n += 1; + } + + + { + hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; + n += 1; + } + } + + while (n < minMatchLength) + { + + { + hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; + n += 1; + } + } + } + /** ZSTD_ldm_gear_feed(): * * Registers in the splits array all the split points found in the first @@ -368,19 +415,8 @@ private static nuint ZSTD_ldm_generateSequences_internal(ldmState_t* ldmState, r } ZSTD_ldm_gear_init(&hashState, @params); - - { - nuint n = 0; - - while (n < minMatchLength) - { - numSplits = 0; - n += ZSTD_ldm_gear_feed(&hashState, ip + n, minMatchLength - n, splits, &numSplits); - } - - ip += minMatchLength; - } - + ZSTD_ldm_gear_reset(&hashState, ip, minMatchLength); + ip += minMatchLength; while (ip < ilimit) { nuint hashed; @@ -404,6 +440,7 @@ private static nuint ZSTD_ldm_generateSequences_internal(ldmState_t* ldmState, r for (n = 0; n < numSplits; n++) { nuint forwardMatchLength = 0, backwardMatchLength = 0, bestMatchLength = 0, mLength; + uint offset; byte* split = candidates[n].split; uint checksum = candidates[n].checksum; uint hash = candidates[n].hash; @@ -473,10 +510,10 @@ private static nuint ZSTD_ldm_generateSequences_internal(ldmState_t* ldmState, r continue; } + offset = (uint)(split - @base) - bestEntry->offset; mLength = forwardMatchLength + backwardMatchLength; { - uint offset = (uint)(split - @base) - bestEntry->offset; rawSeq* seq = rawSeqStore->seq + rawSeqStore->size; if (rawSeqStore->size == rawSeqStore->capacity) @@ -492,6 +529,12 @@ private static nuint ZSTD_ldm_generateSequences_internal(ldmState_t* ldmState, r ZSTD_ldm_insertEntry(ldmState, hash, newEntry, *@params); anchor = split + forwardMatchLength; + if (anchor > ip + hashed) + { + ZSTD_ldm_gear_reset(&hashState, anchor - minMatchLength, minMatchLength); + ip = anchor - hashed; + break; + } } ip += hashed; @@ -557,7 +600,7 @@ public static nuint ZSTD_ldm_generateSequences(ldmState_t* ldmState, rawSeqStore nuint prevSize = sequences->size; assert(chunkStart < iend); - if ((ZSTD_window_needOverflowCorrection(ldmState->window, (void*)chunkEnd)) != 0) + if ((ZSTD_window_needOverflowCorrection(ldmState->window, 0, maxDist, ldmState->loadedDictEnd, (void*)chunkStart, (void*)chunkEnd)) != 0) { uint ldmHSize = 1U << (int)@params->hashLog; uint correction = ZSTD_window_correctOverflow(&ldmState->window, 0, maxDist, (void*)chunkStart); @@ -715,11 +758,11 @@ public static void ZSTD_ldm_skipRawSeqStoreBytes(rawSeqStore_t* rawSeqStore, nui * two. We handle that case correctly, and update `rawSeqStore` appropriately. * NOTE: This function does not return any errors. */ - public static nuint ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, void* src, nuint srcSize) + public static nuint ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, ZSTD_matchState_t* ms, seqStore_t* seqStore, uint* rep, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, void* src, nuint srcSize) { ZSTD_compressionParameters* cParams = &ms->cParams; uint minMatch = cParams->minMatch; - ZSTD_blockCompressor blockCompressor = ZSTD_selectBlockCompressor(cParams->strategy, ZSTD_matchState_dictMode(ms)); + ZSTD_blockCompressor blockCompressor = ZSTD_selectBlockCompressor(cParams->strategy, useRowMatchFinder, ZSTD_matchState_dictMode(ms)); byte* istart = (byte*)(src); byte* iend = istart + srcSize; byte* ip = istart; diff --git a/src/ZstdSharp/Unsafe/_wksps_e__Union.cs b/src/ZstdSharp/Unsafe/_wksps_e__Union.cs new file mode 100644 index 0000000..a90d361 --- /dev/null +++ b/src/ZstdSharp/Unsafe/_wksps_e__Union.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; + +namespace ZstdSharp.Unsafe +{ + [StructLayout(LayoutKind.Explicit)] + public partial struct _wksps_e__Union + { + [FieldOffset(0)] + public HUF_buildCTable_wksp_tables buildCTable_wksp; + + [FieldOffset(0)] + public HUF_WriteCTableWksp writeCTable_wksp; + } +} diff --git a/src/ZstdSharp/Unsafe/dictItem.cs b/src/ZstdSharp/Unsafe/dictItem.cs new file mode 100644 index 0000000..df05228 --- /dev/null +++ b/src/ZstdSharp/Unsafe/dictItem.cs @@ -0,0 +1,13 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + public partial struct dictItem + { + public uint pos; + + public uint length; + + public uint savings; + } +} diff --git a/src/ZstdSharp/Unsafe/ldmState_t.cs b/src/ZstdSharp/Unsafe/ldmState_t.cs index 441ea86..6e86b3c 100644 --- a/src/ZstdSharp/Unsafe/ldmState_t.cs +++ b/src/ZstdSharp/Unsafe/ldmState_t.cs @@ -1,6 +1,8 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { @@ -91,48 +93,45 @@ public ref nuint this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 64); - public ref nuint this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref nuint this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator nuint*(in _splitIndices_e__FixedBuffer t) { - fixed (nuint *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_splitIndices_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static nuint* operator +(in _splitIndices_e__FixedBuffer t, uint index) { - fixed (nuint *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_splitIndices_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } @@ -207,48 +206,45 @@ public ref ldmMatchCandidate_t this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 64); - public ref ldmMatchCandidate_t this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref ldmMatchCandidate_t this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator ldmMatchCandidate_t*(in _matchCandidates_e__FixedBuffer t) { - fixed (ldmMatchCandidate_t *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_matchCandidates_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static ldmMatchCandidate_t* operator +(in _matchCandidates_e__FixedBuffer t, uint index) { - fixed (ldmMatchCandidate_t *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_matchCandidates_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/rankValCol_t.cs b/src/ZstdSharp/Unsafe/rankValCol_t.cs index e969843..7d6dfd7 100644 --- a/src/ZstdSharp/Unsafe/rankValCol_t.cs +++ b/src/ZstdSharp/Unsafe/rankValCol_t.cs @@ -1,5 +1,7 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { @@ -11,10 +13,9 @@ public unsafe partial struct rankValCol_t [InlineMethod.Inline] public static implicit operator uint*(in rankValCol_t t) { - fixed (uint* pThis = &t.Body[0]) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(rankValCol_t), nameof(Body))); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/searchMethod_e.cs b/src/ZstdSharp/Unsafe/searchMethod_e.cs index f7bd273..ec89e7a 100644 --- a/src/ZstdSharp/Unsafe/searchMethod_e.cs +++ b/src/ZstdSharp/Unsafe/searchMethod_e.cs @@ -4,7 +4,8 @@ namespace ZstdSharp.Unsafe { public enum searchMethod_e { - search_hashChain, - search_binaryTree, + search_hashChain = 0, + search_binaryTree = 1, + search_rowHash = 2, } } diff --git a/src/ZstdSharp/Unsafe/seqDef_s.cs b/src/ZstdSharp/Unsafe/seqDef_s.cs index 0f176c6..407922b 100644 --- a/src/ZstdSharp/Unsafe/seqDef_s.cs +++ b/src/ZstdSharp/Unsafe/seqDef_s.cs @@ -7,7 +7,7 @@ namespace ZstdSharp.Unsafe *********************************************/ public partial struct seqDef_s { - /* Offset code of the sequence */ + /* offset == rawOffset + ZSTD_REP_NUM, or equivalently, offCode + 1 */ public uint offset; public ushort litLength; diff --git a/src/ZstdSharp/Unsafe/seqState_t.cs b/src/ZstdSharp/Unsafe/seqState_t.cs index b03b130..fe5ed14 100644 --- a/src/ZstdSharp/Unsafe/seqState_t.cs +++ b/src/ZstdSharp/Unsafe/seqState_t.cs @@ -1,10 +1,12 @@ +using InlineIL; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static InlineIL.IL.Emit; namespace ZstdSharp.Unsafe { - public unsafe partial struct seqState_t + public partial struct seqState_t { public BIT_DStream_t DStream; @@ -16,12 +18,6 @@ public unsafe partial struct seqState_t public _prevOffset_e__FixedBuffer prevOffset; - public byte* prefixStart; - - public byte* dictEnd; - - public nuint pos; - public unsafe partial struct _prevOffset_e__FixedBuffer { public nuint e0; @@ -32,48 +28,45 @@ public ref nuint this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get - { - return ref AsSpan()[index]; - } + get => ref *(this + (uint)index); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 3); - public ref nuint this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + index); } public ref nuint this[nuint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] - get => ref AsSpan()[(int) index]; + get => ref *(this + (uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static implicit operator nuint*(in _prevOffset_e__FixedBuffer t) { - fixed (nuint *pThis = &t.e0) - { - return pThis; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_prevOffset_e__FixedBuffer), nameof(e0))); + return IL.ReturnPointer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static nuint* operator +(in _prevOffset_e__FixedBuffer t, uint index) { - fixed (nuint *pThis = &t.e0) - { - return pThis + index; - } + Ldarg_0(); + Ldflda(new FieldRef(typeof(_prevOffset_e__FixedBuffer), nameof(e0))); + Ldarg_1(); + Conv_I(); + Sizeof(); + Conv_I(); + Mul(); + Add(); + return IL.ReturnPointer(); } } } diff --git a/src/ZstdSharp/Unsafe/seqStoreSplits.cs b/src/ZstdSharp/Unsafe/seqStoreSplits.cs new file mode 100644 index 0000000..d0f11f2 --- /dev/null +++ b/src/ZstdSharp/Unsafe/seqStoreSplits.cs @@ -0,0 +1,14 @@ +using System; + +namespace ZstdSharp.Unsafe +{ + /* Struct to keep track of where we are in our recursive calls. */ + public unsafe partial struct seqStoreSplits + { + /* Array of split indices */ + public uint* splitLocations; + + /* The current index within splitLocations being worked on */ + public nuint idx; + } +} diff --git a/src/ZstdSharp/Unsafe/seqStore_t.cs b/src/ZstdSharp/Unsafe/seqStore_t.cs index 0d193a5..79376ca 100644 --- a/src/ZstdSharp/Unsafe/seqStore_t.cs +++ b/src/ZstdSharp/Unsafe/seqStore_t.cs @@ -24,8 +24,11 @@ public unsafe partial struct seqStore_t public nuint maxNbLit; - /* 0 == no longLength; 1 == Represent the long literal; 2 == Represent the long match; */ - public uint longLengthID; + /* longLengthPos and longLengthType to allow us to represent either a single litLength or matchLength + * in the seqStore that has a value larger than U16 (if it exists). To do so, we increment + * the existing value of the litLength or matchLength by 0x10000. + */ + public ZSTD_longLengthType_e longLengthType; /* Index of the sequence to apply long length modification to */ public uint longLengthPos; diff --git a/src/ZstdSharp/Unsafe/seq_t.cs b/src/ZstdSharp/Unsafe/seq_t.cs index 9594f7c..0e0daaf 100644 --- a/src/ZstdSharp/Unsafe/seq_t.cs +++ b/src/ZstdSharp/Unsafe/seq_t.cs @@ -2,14 +2,12 @@ namespace ZstdSharp.Unsafe { - public unsafe partial struct seq_t + public partial struct seq_t { public nuint litLength; public nuint matchLength; public nuint offset; - - public byte* match; } } diff --git a/src/ZstdSharp/UnsafeHelper.cs b/src/ZstdSharp/UnsafeHelper.cs index 43b3701..bca9bf3 100644 --- a/src/ZstdSharp/UnsafeHelper.cs +++ b/src/ZstdSharp/UnsafeHelper.cs @@ -61,7 +61,6 @@ public static void free(void* ptr) [InlineMethod.Inline] public static void* memcpy(void* destination, void* source, ulong size) { - //Unsafe.CopyBlockUnaligned(destination, source, (uint)size); Ldarg(nameof(destination)); Ldarg(nameof(source)); Ldarg(nameof(size)); @@ -74,7 +73,6 @@ public static void free(void* ptr) [InlineMethod.Inline] public static void* memcpy(void* destination, void* source, uint size) { - //Unsafe.CopyBlockUnaligned(destination, source, size); Ldarg(nameof(destination)); Ldarg(nameof(source)); Ldarg(nameof(size)); @@ -88,7 +86,6 @@ public static void free(void* ptr) [InlineMethod.Inline] public static void* memcpy(void* destination, void* source, int size) { - //Unsafe.CopyBlockUnaligned(destination, source, (uint)size); Ldarg(nameof(destination)); Ldarg(nameof(source)); Ldarg(nameof(size)); @@ -101,7 +98,6 @@ public static void free(void* ptr) [InlineMethod.Inline] public static void memset(void* memPtr, int val, uint size) { - //Unsafe.InitBlockUnaligned(memPtr, (byte)val, size); Ldarg(nameof(memPtr)); Ldarg(nameof(val)); Ldarg(nameof(size)); @@ -113,7 +109,6 @@ public static void memset(void* memPtr, int val, uint size) [InlineMethod.Inline] public static void memset(void* memPtr, int val, int size) { - //Unsafe.InitBlockUnaligned(memPtr, (byte)val, (uint)size); Ldarg(nameof(memPtr)); Ldarg(nameof(val)); Ldarg(nameof(size)); @@ -157,7 +152,6 @@ public static void assert(bool condition, string message = null) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [InlineMethod.Inline] public static void memmove(void* destination, void* source, ulong size) { Buffer.MemoryCopy(source, destination, size, size); @@ -173,14 +167,24 @@ public static void memmove(void* destination, void* source, ulong size) [InlineMethod.Inline] public static void Prefetch0(void* p) { - //Sse.Prefetch0(p); +#if NETCOREAPP3_0_OR_GREATER + if (System.Runtime.Intrinsics.X86.Sse.IsSupported) + { + System.Runtime.Intrinsics.X86.Sse.Prefetch0(p); + } +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] [InlineMethod.Inline] public static void Prefetch1(void* p) { - //Sse.Prefetch1(p); +#if NETCOREAPP3_0_OR_GREATER + if (System.Runtime.Intrinsics.X86.Sse.IsSupported) + { + System.Runtime.Intrinsics.X86.Sse.Prefetch1(p); + } +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ZstdSharp/ZstdSharp.csproj b/src/ZstdSharp/ZstdSharp.csproj index fc86b59..ed1dbb0 100644 --- a/src/ZstdSharp/ZstdSharp.csproj +++ b/src/ZstdSharp/ZstdSharp.csproj @@ -14,7 +14,7 @@ https://github.com/oleg-st/ZstdSharp false zstd zstandard port compression - 0.3 + 0.4 @@ -28,12 +28,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + +