##2|h2{9Mq22l*=45Nkfw2al*04)U4V%Uk znoLGF>6E%Pg|T?_WzhzQjzmRYhIwb}Gkb26{hd7s7TmqwY`ZY@rY!U-kwIrYxD0?@S$-w2UzkMO*wjmYM^J&4x!K zCs(8`kc5(L?>x`+*#o_d0^Nd2ggZO6r%Q|Kdu6nh8erd4g`zx{BcN&-;xp~*t$2|2 zs&xHsU3^p 8bW9X&{So5=~o;Is~2LS=tg%2Gf_YS;>8< z<7i3hVx`BOek2X#z4 k*ix*%BzAaUnOqVu+4xw2lgC;e@a4nh%qXV8PTD>@ zhfHWAT@-G%t}%ev3NP`l4;dtbt6JQLpUFJ05YMU=byH`d6z9;fG0V#w(!J&9biE-^ z#j;u9UHgFM0RK%tyTI*gKX;7kPlW*h`-pws; -_9oVIs@r%tQ8XIso9qD#i7s%=rz!8?6l|)!vqf9KZp!KL~2S*S2kx zsnXnBH|P8{@N51x>f;~|KW9 zeqs!s&Mrs4UjGSEQ-IX3G^quDeu?n<4H$W0%(Ut+Y>i}X4f}4-s% ivLDl2 z`Ch2T5XOWoeS(1%SWo;jTSlTV GzW>ns TaZm;D~&kA(G z)mmJgL54u= l;?EWv7d?6l@*JA*t)B~oxLTQ z&~Z}jNv%N^&Xw?@*QzhP;0rCjZ{rl~+Fa8wGYxXSinkN_M|+v4T3VI_fsx-*27*_< z0Z~(FU1BYa(JgL8x7MN~EB~5mL*2XPI!D>xvq70@!AQ*^Q(556JD0&BZ2V?S?x+dF zpguWWV A11fmUB3~V8NBs$p?)nPWIkgkWQ88W*^9>EXOHOb zStygz3)tdjZFMM#4SZJ6dR^bT8)h!&+rqx6)b*9-A-VeW409=}vR3CsKmS~8@M4Zq z6#yLijwWpOQ-Xc2ya!p>7sHjKVZU&PF1*m%I-mi6d?_S}bzy dbxqL#TTQ8qY ?@P1ZdANK#KDqlj4b 8AZwX z$H)Sr5%}(!ic9SLYrmJyJ+_6xZxWx =s2-va1Q_BlNYa$%@ph<@EiyUO=8{yqL|&u)yAZ@|5N+yM?h9 zQxAIq|J9B*%(&lcsMbP>$mn8h;f?xTGbC@~8*H*2b}_b2Gu6m0UQph4=Yzcp+~@=M z&TB_6B_+W=z&iFh2STwBlOOR;d26zIy3~O9qb6oOYm?XfC6#@8-|pAC1+_`~fI8|( zIr~B?;>6nSw9dQq6j8c}TY7kY5%$phYqK%o%`f6tZ-tkC 4 z=xB&wB K{BpPmTNIT yRYy~;4Y-be2TTg1Sr0unUtkZFpG+9{qxQNUzG=vbsqO_lSHW;|=HRL(iFJuUs)(6A zN~t8ka|f47Qf@;B{W|XIX(ZJZaj8~_EOz7L7nrtX-RcVOe@=cygA?buyou?XB(>+O zds3Xv!sKc4YAu63yJ6>YVZBg^rLw~KtulAD-DZQQ003AslAwS(j!--iKXgT3&o2~m z=61h$RCSTG^`a+X>4Kdz*~x_>z2gdX31s2{8_zs96ICemTA?>_ERfj8K4hgFxLdU5 z%9C@{$x#b~Gg4witCnXfS#V@QTGQuN*N(wfmjM7znJ(@PVTcs>S+TgXUB<=H!SaRW zg!uHBvgUS1;~(o@*EdDG`>kv|5#ad5Xu W2KZ6~%Gel0|QJ{EpHKHC3B z`uJ6HgKK`5G cx- zEk%`=Fda)(+BYs7mq6O_xwhMCG|!)yEh*xr^hXo^UU4!}oEi02hjtKMwESJP`Q )B&MM(*k}DN- z!KRGwv1|7%Bj@f_B=36fmIy$l8%{*ib+H(nyb*D)i?c$BCUr(Z!g_|0E^@zBUjKV$ zH7UKNJrnE_jAlAYDo4g!AIb@*_whC=%}O?_nTqiZmtmh>ERZt^YFsu=U42>y+7yf16=ZeL`YOn#)AyY@DvUQQ5!tAivbp_@yrU*-cZYvZH+?BA zNH-Uv9y6pv9SL*`iVS+zr$JsiI%yt%RNq=+Rqoct {@J>Ba< z^nc4@Jup~~P$}XFMI82VfsBR3W9>%uIa}h(tG1SND$H4xMQAWp@xO%TShR^%d}Sii zD_KjZq9>v8+8(`4*@<(CVXqV9$t$3b?N vsmX*#4lU{F!%2u-RH)UJTcG z=XsH=)-HOC$-{U7lgwvz>T3J+e~E2IC+|HF&1nrRA$*5$JWSuONklaCg=psH099Xa zS?j!bJvk++5O!m|3o?M=wZpi~p2rmKn`zTsN@b;H_Aa4$izZ&N6toa3PC-tps`Eke z8X4ePx$cbHd7BUL)#L#t&eucN!y_`QMC&&VSp`;1b2f?ZNDiz!8)6)PL=rLUk<{q? z@=saa-SE1I^o#NJNd 18XPBQ@fps11+nkynkpJol zc?rMOeT2@jreu6M5$`p6Qody6pepQxCnN#Xf7Z)pMM!h~PUS>$5B~7-YIErNRlDYM z3Bo&e;06KmGx+?bu0Zfw*r>BsSKy)ex2x5Ey*+c_uJDl%UXxv0{hI0n>w7w#N+Jw2 z<~YAs?xbNO@bbT58`I+QFSmO1Xq7mSF#b2ZG!_4M*8a#$D{3gk9I`q-94-^A 1L+4Td;g)H=|cvCjo<1_~)5k%4S+^9ap|<1 >^bSV4RS35qVQ%8*q!YZg$}~)&U p!a 877{_l>&A7wf?sFORfk zp?444%%g9(1O;x)H{Y(h=c^`~JdbcglxSLPX#Yn-tL;wc@B$I{io*)x&%HPkZWb&a zMz?tCp|%Ruz;Y*3f)q?8TG&ODv$zV)<7hF&+tyew^w4>!xQJLu=G_-rxbrW{<>9*S zO%BF128j{7r*Zzf;h|lTPK)hh5~Iy)on=)Zb}})QBM0yGA-GU%Zx+(g#Df_1KmJ)Z z|37zpmZH!ZZ_wtULc1h!KogtzAs1ZVpLR+503ngZ)|7+7WV^y=Qrab1ZxY~1Jk!v@ z6WsgIHQoVZ-IV@4J-E_wl9ePbhK)115Z~zG ~=bQCO8*-7&B5xPN I!Q3) zbxvC@+ 4 RaI|tGdr_Dvm1YJtFf#Fm&yXL(o%XfKAn(Asq3!dgF z?uD)$)egM3iSSak&{Fb$rF#+URknIo!`SiLYC9gBR>KK4PS=O;cC*$`?^c6*Hw*o{ zp+5lOB+Y0~!G&8fdxuUzzo{IkK9`Ua40tyvzJKzn*Jti)>{~gsBm&?F z|~DwYVE(#lY{~`*m!6Q zMP--a@E3}C%7cG}Dmdu>hbUWr3y84^zB0ipKTBtC`bTQjJ3-bab8@$z{JOc-@D v^M1!Xl3 gm%{io zPuj5szN0q@+siaKb%UV@0PU+tw6x?(+n-D4)|E9k{X26q ziY&S=&Gt!h7k9Z~Jm+rE`1Ga+4j?*2X8POxXKg-s4HsP~{Mlts^$a=+mpaGJf7lv% zjViFDD?>YS12#M4=whC|J))^oh;TxnjBT7 bj}t>L*6-zIB#hZAwhVA^~}IJfI%E6zW> zOPUc_$~-$t=llG!+4GS;OBq=;-HMJ!YGg;PdBP^o*B4ZxcUZS4<-J?op}~=DuI8dl zN3GoRfFb71jEYKxw1cyG%Bn`OQp3jgm#YU?EGIs;>*S`>2uTH=73Y2Ha_`G{-dA~F zTh0D*)!b5%cDeNl4B9N-!nu|~n|vLTB-QqKHfSf4aEB`Qr=#2%{;ykNGQtI=5Ba5J zW94R-b{9Z;P6`LzRSeMiPWDWkIs!J>)myYs+|+Pz$$%uwZ!kXIbTYV!(HBM@5LnE? zYB_a&4LkOqP{i-C!N~?)_^#r<%<{OEMm1~>xaTJydPP)h{8J&=*rphgO_=m?6~JhD z13*1ylD{#)<+e9WAEulpoMwAFQdm;z(^EpV@kQyiDO0g}TpJIBPW5y|QlY*3?$V@f zxbl!msGvq-k(@@+(X>2D6rbLw!bTNHv`+zJj|FYLj=96n@C5gQ4{jUR)XZ1;;KP`e zK0?iUd{9zgqn4|f^B5m=CbACNY=F!sIf0d4R@FD;8N$8Z26FZCx$TAcl0c}STilX8 z8lCAD=TH|*3kl3aW1=Ci)h3{XdZ?gJHc$QO96;n$!1!ZKq_KQp1W*I9Q>84p-c$$Q zf;U+{W8eJqJh9-OZo{BFJUl}kCKk@=Gm(6x;fo#(#R9xNRS|{VvK85>yM!b7JR3%; zLxhzV<=-m{u?lx$^H6Mu6H5B2|NS37qxg8GPu|MnQ=0bQ^&yDG!}lxuE00l9Bsnoh zNzg h_}*#mn53b9 zoS97EM-8dCca-Q jDEd}OhH6+`yYW^< zZ8vGWmK$jsF=GYm2gG`Z$1q8|2bx)R8WMPrNKr1)zjnkyS^8)GYCyrf^-)7hOwSpJ z9oTM+R#zyM2e;*+_f{S1nH^SB{Rt=_5;Q&wAcyk|=cVCg$JXV}kpbbL_$bt6S@FEc zQoeI5U0P!xSu;f5I-##4Wt9)(c7FY3ea2>P7={6#O;xtw%BzBenAk&)uMdl^UqxL1 zIk5Usy<_tk3#4VyAFWx#_Fj|}i}GjcWoN>#{VCIx02X_I*YcR9VI* ^mRzHN-px#8tvm^GIpw;6wWI+D9?(> Q-Etk A||SnanWMtTijXhe=BB3(q#ppWjYpIA~B^ej!- zVo1!qb=PptDRxutC+7a;S|9SW{|6%fHQd}808 ?ls_}|+Kre=5L@)5Y;XdmKMp6d>kxBA#kt>$9LyP+6jx;-#Nua&PBVm {y>2eW XBzXlv#Saqg_=??r;LBN&8!oVzR<{rRArSd3%gz zqYS*t+QSa$oiTWD>3vRagPEXS=UrBu80$6*We%3Y3Z?&8!L-^eptACwzY+Q)GBX z_<;m{SILTOnhspT8Ec~fo#GA`>jKsm2s6P}oW`W34xK00y<_Kf-~jOs=PNFAe+xI? zuqm)+JlVpiss1Zpac~ygY%ag ;4Da=Sq9+2vxC|i)F5KFeD>Gj!7nxQp zXbpnf1IUXccMmVtFO{LRqO}W!Qwp+>ZZ$6XsqLZ-H@B$<9!*>OK^Sf!{% X14aDy<@oL=;i}T#7M^} *60 zq(g2co*w{c=vun}gZJ$`z5};?y;U#lNP R7rHsFTmG z^lrb3aTY+=jjQ)v(_(%-A!uoE %jKL7uD?`}G}WU2suKY~wtDoW-7oz6g%f zkljJS!p&Dg3mSLQcKcCm#|hL+tDf-g>*`ALQ z)*I?w4FSDVIpGlhAI*ePW_m!YE`Fv%1URj>VWrL;vRP#{+U&`Q*w-KtTz}1P{m4Cf z_le-_G*1NPjuHe=5Q3kScga*Pm+@e@-d|i?Bf-{6N5nRs3<&mH2Xb|g`He~bZ#>45 z*Y%wQBE>qDG|+XKMFL$?u$1upT+j5CR=?yGX`A^I@FvR2zS7rO0pux5G_Mn!zuEm3 zf_IZ8c~_4R-lt*1>9>hE$^=djeJHf@vLL*j`;4591*mJIW(KL23Qnjl+CHkseWoID z2BV?5^(BQw(?Jlpj6cXJYU6M yfC&2^bcb*uOKg#k&~Vd_$tWKo~#!5>P+$>b(87j8U?wR#Y*4v})f{L$Ps zcQ+6_gZ9Ts2N9Obgv)29zV~Brp#!npzgLGO@BZZ#c~fdS-RrGnf7cw6=0M!d!1x5+ zO~swzDU+G)wI`Rnv_|H@G!-3stiM`1Vb=_v0^-Hjbc|KODtW8>HPN$N=}uY&qjW5@ zst$1r3P=#2C4n8 p{n#+v&ZRWZS)4so3N{yY0P3JHN}$TH0W3X zF k%)0nFJa&|*VfkW&~&)K0AY&=2+iWCVxj&}#f_HtnlsWAgJVs%} } z4xE5ImRe-LQ}5dwDyB{F9Wk_*T@Y&G|Ii}h|G^228sFDnwh=Vq6*$DQKi)3VKHzki zroG*mAsYW@dJE~*jD+OyjwHb;g7P<5Zm*43{SN~J;_!0w{|%}LJH5T3ww>U+{R6+X z`7n(nH_yr1-bmcepFRsNrtGl1zB| sPp8U@WvRe6C8SdKscN z(@YmnZ}|<#4Dly);N$9-b3>MgL)Q7XSBFD3bGCKdlBH|E090}P`rO2 UP=Bhx}*D;ySVJ76G~x8fd$ zMxH9r1un!08|qNb`nvd)+TWyqlX&L3pn650U_~X<3h~XL?&Z4vW(l{?@H6^v-rq|N z5FFgv8kX5+<>J#2ZpAN>we0)+JljkqVcyb7x?#~EB<65j>3CbEwS+gl_B-kiqEo*0 z;9rFuvAeXs<-7475E=Kt9TJEf&7T!7OzlQGY=x}0!5|6~pTp!1v{tKP$xVkA)wNBY z+OyO);h@NhtgXe|aOcklV4v8a8jFC4!)~DIfSJC)I+108?hMtRu0g s }@`RSH4JmpYIfl&yIUHI 3pG5bYSzYz)FC4m1&5KUdu7U$k@IEE- 3Y!UQu@Lsry{cg{Omn8dzB>ITeIkJ9n ziGS=h2Zjt9 Qc>}B%hwBTWMc>Z1Bi@%mB#`5_8m~s&mhf&vBObPZl1~ zre2~pA5Be#^$~}113zTq0RL5K^|x @W8HCoEV+d_b{!@qsjjr zpPp8?TBN{?67SS)^elj5b6pEA#`C8uwe0Upe0FVIFQ2$)V7iu5c3PtkhVDB4KnC6& z$$8`^{{oTkzj3eQp*G~M;KWVy@agV+sm{ZvOO8*X7R3)G_gR0~EO}9cS48P+q$0+` z&8^`(6)1>^Fosf6&+$c9Y2z{Xl{Z 57oN*uGk)6Ah;KruFg{#}(XoeSDl{)!OHgyxeHWuM5^oU&Pd6 zr7&?0s*j|&J=NHbNR(8R$WlR3bDxnmIjNvLsl%FYDQgj~hDJ*jP7c)G3lD|#&@Szt zkkdjF9n;V7l*S@oYn0I2%WziIYB_JF_c1_IEwDoJTb!^Gb`Q1c_3POB(iw@WY$V^< zjF#$a*Gczv-lOhx?Ixv2@l{R0DG-cw7dx#h%-|hHV~OP|aVG@Q;TXoz_1hHjXz6Lx zby%ye%xh^oc#vaJnaoA#`(o|mC@HWct|J{?h`)Zw0j;8B0dmAZdG+R}>~k@w{TW4P zl>ZKH%U}Lq$QTrBuf(XqYEMmE22ISrixH_R8vRQyz*xd%rbYhdu1P31dGt&I7uS^u zvX|E9-l!MX*E4=(*eV}z%U+!;^EybGoTLKop+vaGi%B*IXLurJmfjVV2%MI)VPU_E zMR7c5JV&4|TQ{|Hrr!@)OMIHQ^kyb5*ax~7XBf>rky3Z0`2f@yGie%B@JTWw zDhaX!h~qH#i&qa``Ldvi3ce7Q)gp^)1N27U123EDN8Te*uBW(ef&Bc%VFbUMAJzO5 zYt(QU$u7{qIk-Xt{|T7uyyk7fB)v&}wg?gE&uAc0TlL+0;>a5PgJv3QaP=zGk4QJ&1m|N*WhyM)2(r5FTK#b>)aPUot&3Zbn0S~ z*wm!1up=b}pl_9hvgD6-h1IRZtvDT;_PP$9NRPcyf9z9lUA8hn-@I|phiY+3j@6v$ zeh$IVyx!%6D4;0o>wxTL@;3`w1u&)fC-NFC@c0V(mkFd{)1mnEOa;^w6!70A--1EY zUENx(4Oh*BHRzd!IYrAF2W!vGZF2flp^MR4?USTu@_+bQn+;Q=eqFJbF7l-Y9{^!K zKNsmg19g35*X-gn2`HAiM`!xF`(rpSGm|=MH@cIPSBnbu>*X*(HLN3PKzGWHcXTu% z?GhYI8j;jq526vvkOckQPb))?Zg+?&y;UTGJL}oP%o{;fdx h3>Eq`<39E&TRK&Y>z z9s=&7^lhtHulQ2=w?HV%+Ln*g3wD&-{s7zbkJ)fXIVZ{Uu64;gxQJ-QO-;UrZTw&w zIdabSr>m9GIX5lTa^gas#z-#J-na!>^9s2{dC(9IPTc@a?$k}m(}69<63o7@o`F;C zZQ5L8Sa0`hxFFiZ>ps61sV8{}+krdbd8=7r3-?uNZBKD+O0Hz;dHv1i0HD9K&&+V9 z$#@{*>qw _f%g(RW`(Hk#73&46Vpt@wTv@zeDEDrCn$nAeXl8Z|4v3 zR+#_li5xmM+0QZ^r0V6*3-(#8%Q$$P%~QXG)g3kcli60C8iL4=c|7}yO`bXf0fEyg z8>3;B79lz^=yPNp8cE%~PC(z2*A)+!zNXc11|lU!73PSDVf!@ZRFuIbEA5kghvaAC zQkx3-gYdn$r6kQv-L^H7Xd-Io>{^0BC%daeyxAf<#Vc|3P2IE}Yi-Y(H>2*i`(*BK zkkQo@G;}jS%xWY5KEfjZh&qDn&Z>o3JDzuvKTut8iCkb(>=tF61v1fymYZOfGlc+k zbZqO{mnF~MEQ;dQtP}R4oc|}=ZxfXLkd80pENRu7cd&0!#wXA~B3&*!-;kWQdNr1A zHYfc{^K474ic3)8Za_1;3nn>c2oqs^8o~^sc-6WX(C7H_63Dla`a^N-l4=4_g}K^= z{3H0N0!*CX>&c@3$TeKCa}n;q%OrFZ?_)icKLQmJ^EiF #6+eqmicyxQ-O zO$-0O(s%3>p1%jFH^nvhUEo5C5*xDdtKs2b&VrCza7*A~7nDudSZMPFTIokhH%EpV zq`gDdrgGUMA={e|*X7`W;}%0Gc2L`TEukJVIt;oT96@Vp`?d_2Q?<`hDa|DKOa8UY za6zD%ez*c?rk_jmAf%QX@)>vsZ!XcyeRgY6xhiS?D<(4M&D}!ye|-*)3O~=FN2w7~ z%I#V;Ip|0*bXjKRYhlS|p~3gSx_2v*b{?@r)GT_r7H06hZYmn>H@+6oxV~Wh*#g!E zP;a-#!)xEM#tP ~P8x8!e@#ghgYo_g;c_HS~l zA?C>HKQ<+;e8DYoBMg}KzLfQ*oS`(bRX7xnhgPd)T^_q4sS} lJCk<7V_QGWwj6M)VOI zxEe~U#++arAr(T3VAoQHF1!GTqmPPVTkbyZw{2& cxE=1$&WF z2as3oTjsmX@A3`D8xz%^$e)BiHj{!eI@auX^yo5}UuF#Hp+R2;R=tw>n7k6*JT!8W z{KaoY1Idchy=oSJDx{P(b$myBGujke^%q )iriY*|_}{%vWS{Y<;OEqkn+{+4)%l0O^WtN98kZ0OEr9Ao{1|#nUCYSYsv8 zA<~C%b&kNIl5DBcxw5)Xh$b^Z;l%oKdD540jfeib!~o*7)BzP(c%!+=0!?k&qdZzv z(E6-8TTi2urZpB<-WdtvL?3cn$r6q6@in4Y0Kg^CKiXL?!W2LUW#<%TJf;SI&Ft8r z7the7l*h%TXPu>J5_~|f0OH#1>#oH =%Qoxn8n)j)7^t 7MT z6>Zlg39r; 53u~BEQ)oRhalpS343F9-P~#Z&mKD!5En@}BMg>&$kb`|j*vbMl~PVSC43c} z5PkNXXaE0U?%l(o-2cAs_EsxVtYTtNNTmpi7zU+CEK)0brVOSUl3ikKk_yWh`w&K@ zQW>V9QFf!Eu^S`Vk6jsN#@J;ugR#56qxHM4-+leA=X&n@xu560pZ-wCVUDBoobx=t z=jZ$Ryx*_47t_uQ2j>tbjE%TgO}0Ug?gf?00v_hYGH|~V;Gv5WHQw9XwpSu|0ob6T zVat!j bL0Nn_w@R!4UNSazbg rQ- z6!}^~4gKY=i|SMI;U0|FmR+olN|QRGVr^$g*aO_uTbs4yqPU&~lD;oTFyJyfC$kid zn<_xXelXa;j=kt@T@APjWA?E~FVh#?vtdP9z^`z`X+#g_jarafA-9~Jm+LCqW
xCc_LAa^H`o;#**G)M1Q!wJI0!(JpIah zt|8l`v4cpspITS;`q?oZsE0P Ht#sGoS?WId%L+2TY3dQf4oDr)@3aH9 zO6RBrSC2dV=@24~Iz~G){>oR57}WijnJ${*iVn4{ZRwCB|7C4w4V %;9p zwatN;`8bRBo`m{mqJ$d96I|=cI(u(0+r#<~3(BeUNj+8Dznp=iGiTmMWBDG<^1<&d z$31DwQR`@fLC{?P2%jbKX=1fIR-DGlB-tEkrsF7KXXB-*zm2reYTTXGLJD+n)CnA& z8AZ*Lnc!bK10R`uH0UV#C8upc8}o3((0T7uD&+;Du!O#gS7jz*)P=%m60y;C(P@#A zU;Rn90OK4KzcK6`Si|CGTdf6;gLLGaLn7oMJf#N(dd<2{{J@u8=ByOGm`N> w8jNNr>6>)d+d##TMW-l~xyM-F` zKeFHLy^{7nN*yQ_Y=9{mCj4)L4!nQ &0@V*h-OKHl`-+ zC+*EFnVeFE$L_o}oGqw56oRYF_p+(`bdcWa6sBw8dGDUw^z)q6SNY=4u-2)u7rC0Y zjgV@tUfg6QvgIMsg;UL7*GNY}Fq%2M^)3xM%&0tE^C!5V#NE`jF=W6EfD2w^Z>T*a z`{A1)J*m}38k$GjWM+2%+nIkI$V-h_dMasRD4TcOt2TX;aaOH0p<6y}``dWmI}V|V ze`J&5HS3OL<^?R>=mB!7FP*Woa)jU^NBLjB#7=6+p=e-&JZ@at86Pkn8&RO$B>N@8 zC$CwL=fppAiy*Fw;TV<1cYX7Mqh^Z&(=uHk1@z7BWf?tMOxIcM5;nOOB~E>AROe{K zfS2S`g%StOi?u>xPRW%@-<%I#E6(U%ZJ~fl8Yg(i3RdNtW%QH;g~gZN&y;g#E+t_k zLu|vJL{~-o7sX~HPLcf+jtOBs!uNkP=T+cjX2cDEL2#PNRF#%lDba~(?*0?fWjY^B z;6u8$X_9HW?Y@@K0B=0`e*K@h-#Cw^j+d~toux{qQ!+eg;N_2VP7~Maf1@l8i7RdN zvMPB%MQt}WqejzAk_(P!5$9e7lD$*wN`HDB3{%*;r8W_TQ}q`SD}8Q(32|`pjUDdu zL!~pStM~fq%7|^@ohwy&(TW}nVQa?5hYj~QLwy~%@F6? =pcOeUbX9Axosd|^kKAH*1bD4`g zVj`Mkaa7B82vfkTtn(~}4E+{&?@UT_1x&~Pcii(za)#>qq`QiY`)ts;KYG>C?xW}O zACR>qqSO&hPJb2wH>Joy5~f-94h>&)WYX95EqXgr&Sdb*?JoxXgNR@XQHz~DTV;Il zgkc8#{r_GX!m5FNJn7uHx=ZwNMrAogjR!}+5WETWIVH{mvJmP$#d1$SC}+??yuLY9 z{H@RWd=xC qVO{7tjlXTb8{h0--U31Fm*Y@EUc3b7lu=oz`qdB-jKLBMN0(T?SqXiAbwPgcRa{XC;Fb7~yX`a`Y$YjlohdAEmkl^?DjwGT?GO^d z_}ReVvvvSMT+vBJv1einqdzMsptkNyW?cRFPXP(2=yn->X?|Y +jgkYp!LLb(+X5Gx>l4UnOW}IVjs=Hsnbk4 z+jOl$Jt{3i6ABj+NkgdRO^h$raQ0jQY;!T`KC*LE`Aaq3<~b7+bF4%=x9YyMuT=z2 zn+}a}nmbPQvgXi6e}Pe@1H5oFbidXsnWdo7B6jUfb-H#bARP&dJ|h2Np3YP=rjA%5 z1U-e@M2B{M#KSqph{CajJ(*}?9M%LL^2N-hqiuSn{!#v5w^2Z7f^}>BQ5}y2EB%$P zB8mBi9)go;uR=DubnnX2swTnL%~4GI{hFf)y_%r<&?~C!){85f=1g(S+hFx5(B9U% zTMu__Y(YCK?%XnkzdF!r0{2+1*9$w$mc4GdVd )J z{?_ggv8QV<*`lyn?ZzA6e)^Ii=4QOs<0|zJRKq`RDMBvLV~h~e%GJOYI<;&*#IwIu zr&ok{ )g@4D1o&IM(GBGKPeI@EKT}r-5MbZc#JsBoE%D z6hL*8Wt0wm HaI?X_| z>Ta49+j7XnB`vb{RpTXzd{{opChSqOU0Q$dSb4yZXvwr0-@SY+dU_u0>d}J<<3>5s z`2Dl80Q4~5cnow;)OT+ZFVQK(8rV1A_%x&W?a|9Dj{mGP-(4}sdpV)jDc<_VT_>6# zI~LQAWUj0j6nD0SzYImMb1Iw*Do@ArW(2{9TO8BB`G0Eqfa#mKJ-LL7sgzyUfNX0< zI4eXzSob5VmubTzq$>j%O=iL0%HP2WD9UMt;3miLXU~?f?_}je>!yUQ#g72a@O@ )7<2LHj_mFC&fRyi!s9X;y=6+V6gNrP|wA*nU=K~ zqu_b*RqkMJDUf;G0|F8DDhhVsfS>>SuI2CI5esqy5zl5W;LXFQ;}FZ2kF^Y+K0R;* zAUF0$Oj)l@R;=-*R%a{L1ZHc#47GP2p^2<3?gxVo8m&$zOz|(@Y9S3vzq9vgA>q}( zOZ5IN{J^L#533jj|J6Sgv9@##BQl@s?K->9e}Kr=Tj7ppCRDPUn(p!F<$?}kIPY!g zow3UYF*{p8D->|O&;+yh1BkC$+mY@&OFGxt4>PtJJ @p1> z-#C%20jq+p>eU-qA *)>cizp3lM$>|;@$9)cUr0pfUYMr(#!!Xe`$h)yD|7iqIgN!J?*1NL6Jm9y z-zQ?JSH)p6k6e|<7KKOzF}0=+rC^qR;U#?xPp*N)DI$R>T+_89;$9VePgQNsEPDV$ z8u=;>9U+h=ug%J-k7g s|9ykkrH%~ NC&Nr4C RtYB 2FX@Z<6%GUc9FfNvRl>#dq336 MHyy$YJOp>x@y`78kOz3#Tc8qu~Z(|I< z@5`$MOF7NiHy>(#5f=~cj$LJ--1(2iIIqOp4HTZJimMph{*vG~A=2?835QU ^5Sj&!@kdrUyc4jSO~uKIq8H!2QweZ~)$XOIbl2Qkj-g$L zo-z|Jm8hw`u9FLeqZ+WaEe^$*!KdX {F%DxH^n zSczzCxicClMv8JKALWM!hS>l-s!eXj1)OCObZ) 1k9 z?Oi?df+^*H5^^A`G?4@Vti{u+u%fCHf$S;?D9}pQI|1zTeWfQ&N$y9@?Ienn++XGL zB15-3SKoM`6tLK`awtXjMfMgSi4UW7q|$^hrn#i>=PJz3K@2@Y_r12Q#8B6Vc+A)~ zdNW=m+Of`L=ZQ7PbeWa$E0?V!?)% Nt0_s0O{l)hvO)$!h+O{JbD_;%}SB>+Lg5aLCX*nnI*TH?6bX z#uf?Xh3ycuXVCa0tI)((A)Xkt@ETs17raDSLzo?q0t=KH3~d-PFcgLw=~BibKi5^k zOB5x+%@=^3ei`6y`=@*{<-OX|ibhX0^ZJ(f*;z+p Nd?iX;A&empBM#VN@ZS&t)qN` (D$ ziP?g6y7CN|UY`Ye0LR4(8R~b;sWWH%{+LOw`~&)9tH0n =HVj|2YBGW8U1o}QF(w>W4Zb+WN=HAyd$k-x5 zw@pV;c&T JJ<;(O2(j_cbgb=Ak-?g0%D-<46NHt2O78(KblIjxY{ji84H~ zOcQ=wVbx15+l~sdwL-~PEu`nHT>r|=5xeLPdneLkwb_BB?bwq*Qu0M7Dm-a)d^>7f zW;%{i8{ #4UV8;t>}ka;3Zu_7?%t8Jj&YdASvHf~0GX}3F{zo>|8 z)_t0 |IEszSfEMUEU22zWne-#8|0){NV#Ldo_AOw<=v;e57ae z mveq^-X)S@3>QDl+b=Sc&N?S2N)>?(zp}pT zBQE!5amNX^f#`@qE#C3n)?%i~dG9d+`RVNCKyTqC6nvn91{A@sAX=AE1g>FI $)0OWuU7{B&^hHopDg4dW>-{fL9@g(W ziE3I|q<6R+(rcPOrF)|~FZ9f3M^vGLwV(UJin{AZ8Mp){yJ+tiNEmS7Wu&x41_x znYr7B93n2CdVrDb+K=3YW{j GF$4_$z#y=F+;K2{Xdl#1VZ0z z4HN2;+{6JH=*Nz%0(g7;>Ve#h9A01Xh&5EzW6mx!DN}S7VuZupcUJCFU<=(>zNRAO z7jW=PF9h0mmkHiFoirA>DZ4KsH#6lv+hanG6lk0|J#gTET&vU?Zi*a>e3mQ&7q8WF z34S@ev7|HMhg-_3Y~2=Z{$uoW5l%JlW)H8xB$SBA|C9dLl{CQjDTUd_qtcw 2-a7 zuR`d&VkW*~T9rDs+6U%2qJbU6b~{gRfD#NmF6i(m^-Pe+g5;v3?cb YRz02vk|EuWk?^6`aX`^f&}Xi<6| zD)elF$u2+HI+2?0nyBBzE9OK3Z1rMr^&KJt9KvPD$gmDC*wq68CFl`AsSsa&%T#?F zttp;{6~X3q-^9^j+XB2?cRN9{R%tfl`fgx~0TwB __%OrDcwmI2p9&ZE}H$Ipa*r1&}_N) =?Vmw|T8EA=OV-nRK(krCPqX7yDYk7~T!=gdhC}sAxcc*+ z1YhW_xm~kIYLfhl8j3)Tnqkeonn#g +P*Gz*zZ>c7(mNL`%C5hB!h0dpSJB`7=-f@vALH#C=Zt=k z?lzC0J~0a6GB^uooDXmI?oRh}=ZGH$l{xJg^UF5E)LY_ETh9EsWjr}Hrt;GNynxH% zn<-vKK*)){(>G`09t?TB^FT|~CUF~s=>ljf2L$xU?69Q-y+mV~@9{~awWxzpe{`qof!#(LqUYl4u+*`AW4I0v zZ7B?T_1Iw0>+vHGRAI=D1%;*z*@9!L5L~N_T0Gcr==u*nM!4O>hfK&iL}NlWxOBUC zc DIc2eJYnkToSSDEKRC{a zOs(;14k}W=98MkE)N|yb_oL=fZDdCV*k%}uwA+*LR5EEswUqW!Q%DXffkYImU9NmM zKXfr{JM+EbsMg #RTT<(`=5)2G|7Blam1it^)>G*ds7^@3^b(@^Tf{Lrb@6#QlmZrn|oRNOV zd=@`j0WrUvNUr(m)9%0~v+nI!|M8!|eg5n2jQ77oDSjdfLAON*2-12KuJ8cxC%g{u ze_&~tV|}0M(q(Jr2O}a*Tw4UGD~1{%NkbJwAI2kd8bstnf4rqr7Fk)>mK+QijN8Av zas_&uyQsZPte!`6Wjp4%@F?mnYTQfuL|w(kn) tN8xs1MW@Scsl|k5V_;w$9J+8z<`E zugzMx8g X!Z$SF;k zzhjT 9=DVc7>IP0vKzU7v;^`=7;yd7NQGVXTm zYg-CPuy68|yr^fANT4g_Vn25!D-jbnCi9oGKOQ>KCT~0aYJyYeIu15Aj-2jFkr@d3 z8Mjf;@F=o%@B4IkQvbfV+nUyiu+ELXN-su@aJyftHS!n@%Qp!g?|Gpgug>3gF$tpZ zIn;dpP6$PdI5+a>32hm-T4y+%m;k?|rqdN}HcC|~c985&BNinX4_=>rFZSJa>B!8b zi09mM*;6DPu>G#$aRSRv0A}eYJi=_;f%GT{ms*F}qjS-Ety?np&0%&2zBR~eV5X{s zb&cpN6eR9W@&|p!6N>6F E8l*#GQy?KUf7|;dnw{-e@(v5%7~%3LwmX( zQm`30`_;n8^)^ycaG(tmM`>5S_Mu59 L9zk$USJ?b4?o%sNu;-3T4Rj4RkC5e2rV%^K(Ac}hj^2J9**$f+uz z`TaEiIy&&wQtMditpFMet!>*;GGf<-7i+E3p2DU*yE!2xuZyaDtyt EJ&aIJTlIiUp z&uoD{5h)=7Ljv1|KN;epCC_)$)r ly9k4hVNj199c;vGaAI9)I`}N@cI?$;Dn8qmt daR4i9~)9tn)92Q`=q^7}~C+!S;>_Mi_GS zH$K0Slovvo?0_>rX<>`k^C`kkH*}%N>Q&JS!Z@VvmlBf1U}4!9_7d+#;bBm-dpb+x zzUjyn4rU9r%j)t9cPd;v1DeCnjN$$%Mnj2z);xrPdf8%>kgL%^ePjSL47r}$1FP(! z?326ZO`|^QiKf|u|4kc_L( UFL+ zy6g3X9HS&DMq=&kxW&sV@TlL89rk#eOf3FVCuOBg%BkJucS%aUM(N(n8EMVR-}A$? zZAFGr$cuJi aj>^hE{_Pg+sIc&A|rwjOkaaBuFnON*-u2=FlvC3_9(5Wd%!%Wv{;e!e9d-^9}0w zR^PauI5&G`^O@vEfc0OYI-WQsu{TpNgE2qlc~1&6*Qu^>(GGD_UwES~A3bSwchx~O zp;iR*669xS4M`@SmzE5Q^rey#jrP^3sbb2JXRMR=muSCV-#1V)!sv^?=6SCJB47v? z+i9TZeK_TeQKI>IcMLmR9{0WR<~h!&WbuOS^v9raONid^JAU^u&PDG5$LKVtiGuG* zo!emDxoD_{+*o4G14p}2njMNzgiyt(UD263-z8cXIwJZCdBe{tjHT<>?X=={bXl&; zKB(EJBVoMIe|?SWp$Z?%>8-xvuD=CuR61D~=ecs=_cdoJm+aY1^%@Nil>CB&pT(y; zxSqNwRmKtHn7lZ6-wql0QQq|d>HaDy+E=j+qN>Sx&F_-pzu8k`6?&(X^DT6FpBPLh zDAq;kmm#~H9n?Dvk>KGAKoyrmda9^%AdWJfu(g1sX++!qIC{pvS*m<=2M5`gs#qg^ zerbP}z3_3R+oKk(mdjQ(qhmLgA22DSZhG^aQ6J39zpxZ&|2Z0MKxuo>N2Bg32#gWs zkv1OipJR;9oJB{icAw1t@== QR#*m rFqY8_4i?3xmo{D}h+%Wnr<8K^Z~$EQsDb8sXWs4oU*gYX zOIefHg)Pf3byCin_j;`;_?wGXjQ4EDAd^l%*?GWP^WOV}kjp-V@^D?l`Owb~A(uSg zM)G|r mu148J?rv z0xKFrJ@h(w&8qh~dL5KDeC%h~+^~ZAh86~$1?rid5wgd9RfUho%q@UW1AqJNN@?y* zv}f%Y4)thWv=mlfz;Z4B6Qn5SRvetQ{A{bvtjDaS>Ca!O{0n38zb?bppGEKkB;SD{ zS%XTh!HUn1l2Wq~WyW&yA}22azdHLuNK0gd`%GRDFJDY3_`j6q3y&bYzN5i2SsqOG z*x#eX4R5c*8&|A
{FA7@s6B8McF;EYJPjWFEN$wileXf}$o z?bsVt|6}hS32h2V>9wB4!Bdf0B5zwjpxx*)C#tMZ#5Hsw`KmC`PLulYfp*dS%G9s# zN~^a@R0I0fkF=w(mhk=M|B&FvDbJvmT!Nno5gU<;V-vfB=|7L5Z2cHq5eWeZ!?@Zc z-J4?Si>n~Hre8}ws`jc6^wv-NGwzPus&SN>#IS(~ZP+$oE_;6s`9q)_1Q%>4yMMoR zFQ7#7l=QO-(%`~N-VONv!M|jXeP`Jx@Awa2PTjrT;Lwf2=Zfut^AA6uLUYM3jRU-} zy=JVj3m7@ywZD(6>#b4cL4;fy2>nxZMa;9v{lF%HIG7W7yVKFdmm0cWP&SK4)GNeV zEOOGjg$?#$#>u^zz!0!({=m=uwVJV9j#6$N9~}ljbpZ7MbZdX$v-H`0-)4`00BkT- ze>t^<9om2mW})-nv%y60%N^Mw>Fu){YE1rE4Kc(;#}`x+fD9Ggp=Y?OFto1ph$MHj zquB#K!&DX!#C<*SNl$jJN#?aqs&RU_K!_leiSvj0o)n1(w38+2kJP(MGG`g@E{v2T z?>pDzRS}qi160`M0f)9!$t(ht>u*@J|1pJ8_L+t^UaJxkQR?X|Y-uB{XkU%5+NS{? z4)~v67iqCnAG67y^J}~k9*sV!l*-lOm$S=nEu8yJFQA2C_c(VoQWvWULS8bfRO$QB z2epdWHW6;Qy78rd;%NF<{&9959JMHCbcbw!yV&c1aA>+@SCrzMveBhIVd<2J!25F{ z6F_G+zb)kFip^O#dtyH6N|oqs1yXwqOsD!YeD)Vr%qLoU5*)%xFQwUis6JxnLDd`i zM^4-reCtxoX0*mgN0^8$kbFQIN-T0p#xHiq;Eu@Suz~*p3wFL9V8N^a7VMXQ!GhtV zd!|qNoSFNfaIvnjX|MRP_F09fqdyc }U< zn;>_NQ5)^}u8t!qxDY {Zdd!* zK$bCB;^d;&d0=I&vo}A) (>x@1W >!WBDhE_creT1&dOf>t z3|cz0oBl``+P7!gpKMk*)Yj3a9Ns_1h=ftKkGtkH8TSvY3`rD$BIEGpu{WLZ0?8>2 zTPKpyCZ=o|rD!_L{g%(B0Gk%a7Y392^THfv&62fa`>~l3>&UXj-u0X#!%lpen}_v~ zrlRj;{RVb>Jc$^<`mHF3xUa3x`n@3%nbvGyvSek};-I;I>DU*;$%=MKEjNhXxBg_l zBJ#ekC|J3JMRih|0cfortUrds9}oJyJvosja{piT&?d^_W|vNA);?>*Drn_#%{LkG zx|>V_JKB<7VF6qXn2;seu&IASSgixMYE$+8$euBF{y$0s6PM6U{a?g^Wh-l)`kL`f zb0V @|a;RBdK-Vr?C7 ;LE4Y z?nfup2M81GPKuOYhiW2Sly$nwr}qaP)RM@A4@QcpQ}%xUjvba*iCMA9H13ra$@nbN zQw!-m {dCQy(9x6cK!=8v~wVuyqi?QiA9w7J@J@%+TQVW=)Z~L5weu*8fKV zGJEUu|C4!Xe^b_g-5OQ8N6TqiW)1DaO_HC|{{s5-G;C{8rd(q?@byqZw=Jm-D7Zy~ z&whIn7yYL4@y)gbQ8qa{oOd!( {LQtLY(!$VgAmaT+ zeWx(iEObS?Gu|0&RI*~BVGdCx{G?-;p(Z>Yo0dMt4H}BF6h7%&xurkloWS12)bAP| zaDI$*PRJ=U_6%H)#44;sJ*xl@xqpM}yLaNXM2xfkl@`3>@d2;!I^O2#yiO A zjAaa8$U+1s8}A&2U3?*L_+6w_qREE!3q}}zOLK6uk-x0(>zb5)@c%Z2?8jqk$kJE5 zKld=ZviJnzO)+3CetoDN71}vDP?@J__<-(w+H)!-8YKRq
dS+0t{zCkfF#BrlP$4Qk6aDJ9f1y-cx4tYjvoWV zS^z8Xoiy%y8m-}pIfb$^`=UX8jPwV2VtDiZ?zLZ_m`@LProl7!MO?NVMrUgwF#f-y zXi_tzbYBTJ7ehVyK=YP{XB8eOq*7n`nu``#PCB)MX-3t6KJq?Ch6oX^%i%zi7<~Sl zskNSXh{1+GA!i|`kZ&Iq)g_-8>IU!#F?fRy8^jfZ7hdzO$evJWGSSvtz=`M4XvFC! zs1c0Z!&7qS3vf8m@IN!pr9oDnS8<0n>Xgr1(D Qx-<36?BrPc4YRco zU&xtyFDdLt_vrRmlU`ec_l9paU#Q#h8>!xY?u s SU*dByn_sHrpCt{X9aUXlYT?>C}h z^Fc5D8 +FS?Jo?Ak2i;UBUg6Y{*7VFS<+m88>)BDR{0NZ2eXg;TGYfq z&Bn_o{4TW|r9&~_7)y`AUab$;?>=jBFIpyp+~6_nahFMOk&`cq6 %N888)kTt z>Y}_cjbOdwKD3cMr9G40;$xY_;j;t959JS!8ZwX7i40C-fHxg|6Noc!M^uXy?WY`} zk?COD$@gs;pU8ABNDpmY-=3-ir})g~72rV6tI z4z5X-#Z*p=HC3G%j=t^kbF;e)0W;}g5D^gZUWz8PhL1*UWJl!Wl5;;R3MgA|u_De7 z3go})Yj)k-k)z02699bmY$Q3c)GUjb245!5A4g-XCIiFZ2PTirBc~iGD(SSX{DBZ+ zg^0@_s3U*oR%yBwFP^*ogr)4?$ XvCWV3IV)`X!f~kgkQT>h%U333y^r> AxaQhOcLv-+l`Sc(e5lo@K`pN^Pf_ zxsH>`x{tgl*mo4MsRju8(t2+|ZgWbrRI*eaglVBOvh05qM}{L>Hz7=g8$|21-_mDL zU vn$4VX=|* ztEQ^Ht)^=A0=ug=^A;;5cmk{M_W;p;rmp^wb#N1r?l|# |E=GTdIoXvL&rvQ(gcJxPaikN;p{EEd3`r-i33WA}AUmCpb9S|lz!hN4e(h|GH zuEdFOVCZ=7(b3vBGAQHu&Je?=A~3Yc`Zwab4d^+Qsz=TyE2^K3EUoX5@?q#+c!B3D zk 5-kKp*TA667!mfPNv0CqE$oOp$T;!L`=ZT8tPZjwaLauJ6$_D0)V5EjlR#)mu z%D6fnHuLoGSJr7}e8OASsZ*(@+&4x#g;eM$ed`x$_OtAJJpelCP|!*?>yn$(s+&GM zV>S_sc*~RoIp1cDoILf3_RkUABpGD9Ay<{8hx9$Iqht1>D860+5m*S4ygzh4%C9WW zuW_JUw1)Djx9TfV*&Phsrg{3nT?5UxNRc|g7U9Igu_qxpZxb}=< PF=QlvhIlF^GQ6!iWpP#VPM}$|+`IRNwu)SYK-W?W8=MrsmwM;!A^nrQxka z!Ctu{WRnM1-DPb3Mx4 (RLS~KI0)G=3kG*lg5dAkGF$U764Zr>PuO|~VeM 1!b8+ZU*e3@H(O6u)I52eRcgDq@Vu hd{}#eZ)A7&;&*%R zGB)E*8XQD(0?P^QDid4P-SWO2d>vn_)NFwaBNXo&K)9ps6xNavXVIzE)kB~Zt4LP7 zD+;0)wD1I0(21Nex0d-NU+4O64k%ai2DG~D&BQ&ZgMVEy^CaU F2)pN+C|_5oEOhguQBs#+`DKVfjUtM T`on`hb{@khtUfa+N#bivYGuub%0uezW8?84N{BlWN`2|`*-``>atKJR zOl>va26lpI3h=fE?|T>FsdhA7b*;bMN*GqneKUXRVS0PV4)lG&^mB1wIL*uQm+%Rl zKJzsm_95I|x`N*;BY$;lfpQ?k)B9$vr*I|(zUh*gxY?fW9A8z`(is`e5}n6|S_qV~ zxGkU1dELH8vif>|#&0Jd5Hn-74cTa892t=&@4Xo)bFOQZ9|rr4(JwACpwS+=3b?3e zug)kRC4ClgKO1Tb5dc)}r%cL4xi0Ei_b)DbNO=t1UCUWtsy0#wM_zr%mn0s;+$r7H zmKmQz-iV ov-Qit&3^(fs zWyWiSomUHH?|ZjHU1Z*{m+E!Zj(g4lsuCx@FB4736Ul_FV$&>x;6cKj4A{$b0RW$B zLtNlWV-e*xJNL-!r_Xxo*|M`UjY%-qzC@;M&Zv05l&zpu4gw (&a_m}G>7*!ts znxG`s5Z&I9K8X&Sl7K7SUr?EX?C8A;PF|_AReK3FhtY&qa}uh$PdSaBv~#zW<)NRi zF&2l;_76D1!HB!Vae`seHzXAXQJ(?LKX@jJr3TnL0c|3jgwV!IE%@e}E#y{`a8YB2 z{93PgkqzZ{QZqrgvd_e^OWRyZ#-8qg<$>aM>^Sz;z+ElYCL969iZ0zZm~2l82w3 z79Z+T?)uyq#2{@>lp)5ox2X$*Wc9MiRND}k#yB;YjIRh;U+{-{SBe+6+#YR=+xw%~ zsW-T%X@_0?4hL75D%0Y4aXZn~epCvoc*(QfTEF}PD|>h&uRBL-(nhRkdgT`_xHiPM z)|2tNSxM {g>YDG)ly!NK7w}Wpj(V@S-u#0QMA9pD1P~-!Iwx(QoCtvC1Q+dd zSH(_ai!pM-i@yFC@7*12de$>31|pe#;6UPO-rf?!_0{Y^3&?%louq&2LMCxl0rBVT zrEddM+v4hb?^i|MoBfZUy0A71U+c@s)jHlP+`$R|5J6JW&wmMj{?F(?f@d2%S5@ZM z)fGk)f^Sjcl!CPdW OuH2XuC(R%pW%#sk?I`F*?_;?B|Qv+9EiVxmxsyLh6%;3LgXAfK_ScKK> z6|8dYszbQq-MT68&BTx1a-;idafImgL|10>@cO-V`ZS$&>0p+*M9OJAd8 `RY|3Fz@`DO%7C8oY^O-Znj*m1OCUQX=G<<9dJt8B3-x` z5C(S8$Ncms!p H{Be(=iT4?nF67``Y*m3mozNx}1 zPse;ehA(H3Cw#33eY88DQ$SSf>V!{(zIR3Yi#_w%M9j>~@T$(+Kdn>sNWWQk|38*l zLOJ{U#j-f1ETg6IlKhZwmE3dk-`K~_N_V;Y&b2%) RwC9C*1Ce$sv~|xU|<)lde>H}mj;bGY9f~8!2qajZFTL(WCTE}vh!!8 zrq-%TEscX1(qn7WdDT3Z-revYQ>&eW!PoN!yF2$cBp+Xr&_aN<-gB61mzjtA)%%hZ zNV=U2vi6omW0El+x?+VUAN;DG!3b b%Jakus3`{j$vbJi As;HKouzGReuNKb5kr@0<(`?0x6SiIRVqFKb zn4k-L4ze_JQ9N_4{(g_Zp)lUY%wsNR##XdyTY^gzB Hb&i+IXUwOEey|6{Lbwz z5~WI!<_y1Qmhg$Sl{Id}%Aad%i%V G-& zHnqHxFX#?QUEX^=*|J1wXP}a&ZnE?D7%vHn){j2D7v#f?o#xtW<-N_@Pm6Cq$~8C8 z*p?`IZYWx3_-n$m-`L;l?eE;+CT~H?O4y`cn-c8NGZw38+v{fSRxjNOfe1%2_ruNK z4k`Mc5{D_4{AJxWpC+fPO3xv|C_z`S-CAhG?j>&U^6L=&vFP$DvhUyD%k;H>|6YPD zbo`YZEZiOf+CrA1nG8a_i&Aj!CgqS^#IMM9IPy8hUA;$i;Y5Ma4v7jSZ!{FKCxufO zm})*AzEHh1g>PKTUu$0DzjCg=>)LR9?{-#wewb`E;E_mvNiR-&_5` N2uh>|F|gK>aqkk zD_-nOtj!^%c@+gJszMmlT0e?gQD6>1zs!wQ3Dm zVFYR9$)Sfxch82tN4pMQYff+BJQm}&c(jM-xX%vw9B aXIQ=uke_tjzGcwW_Ct61uOp< OUS$s}l(J@>Xi8}FeJUj9>Z4XJ6Lu-hN~v|Vn&y#J?z0Q}&3h#JyS0HP zx~r#SAgT31Nv1Lem7U~eDA3wRz7dC6#+s^ZOsQ>JxQ~>U)OS+wSBgw|CTC7;52T)Y zv6`}Y%r^W|Vpe>UeNMzDBmNmr)mH7O({f=jfZ6;@JX<3l^*LA@EEa0xk#i%u=qcj} zO5f**Ww)KQ@Y1J^Eeuc^qr!7E5O+jDA*?mP)t0XKTdjBC0LnkzUCZ2^BFa)ZerWcR zM*EhABR$I?*w7%&+%cv5xzB2fG55NgUNKMgYWSeu#GAC2_@rU&5Y)utC&8n8Ex)dA zHUv-1Z9SFXrlixyCuCnK+F<*}3qbq|VKwSQ5#Bf%K&aQEjFWw9_nv)}m}su2*}uh7 z0;3dgHR{nqlN6pal&ztAFtRoY=4FiY4|ow#Z@qiV-2Mo6!V#0=0 )Tex0m^n^6zh16@YN>No>b-)ega^6Pt5r;qD)qc5^mG<~?-5 zlZb$OnTRfGqGDk89a%QVa_yV;0_wc=(%*Tgr-!{JlfA4i=*A+G8<9_g(!93O%`aTE zo`IQ<*oc9%>dEE{QlJ~5oL&oEw$c8+rH1(WKEar0a+CRw2TFI$f9(oUaByAcGN*lEsC*sK}S_jK7w*ap1Mj7+hHVJCr{a`j!DH8jxJGE97Y@$^RZ#HL7Hps?n z0bRIYU}II@YF_Qbh24X%;Ypb`9ts|h!&qI~8mAG3(Z0?<_AP5k5FN69HlONy`jmT? zUk
lB#)x7NDx<+T#W2p=Y@uD!4QUDy7ueeJ)#_mBB& z-glmP?&p1<`@Qeqp*kWN_*7M2gGx;Mh9a5k4xdK}Hzc5eSF~BqSFb?p*d~tXD-iZ= z?HhURfg5ym&Fn!;SX*pu?u>PVg>zYReW 8o>F zqO|t8FdKWfi>^K_1+s*y(Ok&(61pbQpV7DfS%r8p1^ln6a5+4)(H`DTG1eEQf zcKaGUSw{%jfx^8>>Zxp08M?#3WyT-vl_>V`;HKhRdmZtrcHy6*&E4zOL)$_ZE~L2( z?a>kSs^$?o;)6)|ZEMbb{uxzXd{0Q%dX`X&(AIvfyf^H}i-rHh-cGA8nCzh0YS4p1 z?Y=xiCVqMX7+?^e$_Ah!T;GO*e>nHx=EGf_Hsl&;JiJ$e%K6 zd@%~iVyIpXuDkC{Q^-}Q(_B8c1E}?L4R+Rgdbgh((!jlm@I5*uqUM=}4tYdGmEcA$ z=(In4Us99-1QsXHbVRQftaXg`E6 f}?{hQhYfOm%OWOq=eDadNwuc-+B*`75{mc8f2_zHER5u^XKcK>&= i=*t(=w8Thj1}dYlEkp^cq?u|(SL!@m9zY0=&J+ZOO{s*c zszt~ fYDXJi=|&Gid;nX89I1et3KN#{mMVRd16w9 zmP5ci!%1|D5;}Hgaq2H8XM+KxLP;i*X6d+r`z{XifY(jE&^ER$fLUt(eH7`y*5>6N z>+`!)JugdQ?DiUXaM7W#8U^7Xgou=FSjQYX!Xu;z&MIkxLh>`SuRDrnTV}hU@psE; za;Y&W8(bz3J7jlE`^J@{<&n=r$}t5h?@CII1-X%g3HJ%}1;f6>fliw_(zmaX5UGfw z4DohfvcqKET<<-J8O5W4-V=pOz69!QOR>QU{lSYIrS(l3iW*7dHSZ`vj*;2oxva @}PB{s9%(M%x;@=cRzAk^>?x#ZU zHfmz`NLQFd?;qcl_2TZ0r+PCnnPE^MrXD|zA@|-$?g|umb*8=acqls{#57xlPHnUM zAP<^y^MR$WF;=v`JaqTk3qiu;v>C|m4Qsu;;Dh8~B#Z%+gRq3jvFSdGwace$&+C9L z#+KpH>#eB6uA=llo?RhU3+@vCB}y#x(saMFGVKwzAo2{8KAr3`FrHNNY8v&iAS#2d zaLrXPup?uouBEuBEj6Y=Yc@6J=v W=YRZ?d!gwmg2B)`-%Yw+&o{+nO`CDl`y);H_ zyF6Rn&nb;HJ~;T#p10z(kj^X5BIW&nmEMO!_MbR4o_r#JBty_=gbx`~zxG5ULTxm1 z^cG|p6j5zPzQw$o&?H3Asl9?47o$I;B0He@ys&sF^Mnq)&xip$Q~Sj<^fllb2DICT zp Z^h;SH-N22t_bmLAdlqU;G8@( ze6UtgC_G;Dk&fD%JB#)^k^?i(z2?g(0&l3d<)d<;k*KxT58uZ#0)6k(#fC36)qWUo zCvOU Wf~Od}uRYsqf_W(<6$3%M$+P-|DqFt3daK8J{P5_r(247JDzQ(< F_>gPdo)_#4Px|l4+Y9 z-bK)oc|dvYd;_;o9*iP+XDk|oWEC!<){+@j!W?Xkfvr`yDkaZvbh3{~%Ep^d1qH4d zS#h*%P$naPT$C9RVeAn_WfR #8UiD2OpEocGG@$8R(c0!u#Pab+^8MvOH)Ort+qwdq zkZgfGJ(%xp6F!+aC0kVIK*>;PLmV!H$H5QncqtUlBU-OG3Rb?rcMH8L=EYuiF;EKS z2khvELm*3+VHaELuFUvg{pQehUTkuvI6A$e`yj$O`8H}zIn7O0ltlfr=KZvGQ89k% zOv#NvzJF{OU4eU2f1)@L4|FZW_Gxm{mXlrHO4As9VL{JOEC8n{7WB8c$tDLGD+Rx} z;;DnFvj5oPxf~lBKiz bEXbDC1J0z>6Wh0CUVlPH>8E$7=0h@~b0pfhuA+V`fj zl~ZZjZ0bU}O3)($Rhn;5mAbwSo*yJZV}zJFfEEV^=QIj}b!Pga40ESiwAax2z~VWt zUIa AL7os;Ruc v4pS8Qk>{4_3-@@{ttp=59{c$bp&8s}C`gC7;Zk``SZs5I^Qq=h*~ROW>yEut;( z@6*+!Q8vivVbcYweNAN$b-kdmy`gSx?8lFQfN)+`tSBGhN>Ax+EKMH|=N?WhH91kh zt4Qk2Jm*%>RZ|zFBFO7}4Hk_IE15Ec&jm2eOvvb<%*~TSbV@h*TKh!a%ou0vSPWNT z*;b?8tDq~Lk a=FR<{)0*L@O%_fzlRDc{=qB%~ejo%*ZeV7AX@*OzAUxWzbH=dyNQmgbHf{R!v z*Cv|ZB$X0g;XSQl!Cl{z5J^1g;K6f#yGSGBpg|nlc2m%=h~+}#GL{m`NSQqj<0qJ& z0mR^Z?;tWJD_i`+~hKR&r*~xfLQ@{?(5d9(Rf&FFLNAEb8_S@4pBN^w$}AIqBRF zwNKS|F)Fi2WpE9R48G{$LU|6|{fM6^KW@^RwYpV)2-5+DG$-ej@t8k?Ijw#PcJo5U z6{N=BqXAm%HwXvkbeB>d9V;P_T%zea0l5N8Cbi*b#&p91++?Zd+7eN$dZPfW6OqeD zRsjZdCZ7R4YOs5x>exO6<6JTX5-QG5b7&KdiRw|O=02dp;jE6@7MD%<&6S?sZx>5L z%dTAY?!sx;Tv70IEfbf OLm43r=hA8P>p_14;B1a=!LXy=eS$J*|)( zAjCT6+|qHb327ct8C;!H1IHN(S8CN_$nO32S>lU#oCD~6UlNvXsiYyxfSRR&4>0W} z9B1d_GZecY6d117*#y9x9ejIcMQt=pu7l>>PHsHoVPp^YEV$2_^pZM~XBE)iUr;^_ z{{fa=)~mM6N#sm;*Fz-&$oJ|QRgs0kqntWbFh75z0mn@sBbs}ptOR=}IWyaT+mEPt z27B%^{AB-;MrSv~QTQm9=L-r&6iF|W_4urDV|Q-h^dSfqS8Z7R%&2IC(&a0kHej$A zBOlmrq=ofoMR{R*0y=j1)#qtW>t1~qAts~xwbI_N{;_!Ic2rM2a8i}Iwmf?}ZhgN- zNxbbxj!U=>K~BJlsMdYm+qp3ky>VCh58e&RtCU(GZei8$y5_b+^Q}BcS=^51Wydc6 zg?S;VJ$HtzxBMY;6I%c>1SqkZA)Jf( ZZZ?%wjf2Q#10NL1<@D#7v`05_SHkHio^iyYZR*azLbwn6 zRo_6gm5G%wHFy29e(>X>ORg4FQzA#bhq9EQo#ER)F1nJtF`#dvKL+GjX>z;|yO6vS zXcuIt{-{z+cG>c}B?yW|G)%^MJfrdy&gHGiaK44`vKw~wZ4uoUgCv~vX=ls(Q%taJ zP4`DV9Vdc^@s3mDe4vvPI)hR EcglGA9B?b^5b`m~c78;X3C6s0_}F{CB;Nn2 zY{pz0+u?W-XDcGD$uj6GQ*!jpSN|>v2aV}Q;WxP^iX&r*T=|LEMni;e_y-F3pF*j~ zp7;LG#O580gx=W4bks{$TT2i=8zz4VceS{G94gL(3YB>g8l*|gL*rMIn){x_mVXF! zO=_(_+ohT4be#BNB|%G&MHL$U=HKe$>JxWzx+lVCB3e&+63}v>i@EiKG$Oa=$yDUx zT|H9vKtA7&Opkk$yl2Ef)>ESXgY~CzL(Ozl4!(ejG*{#?qUeRZg8?LOr*G)eOdal) zQt{r7*VW4{gUhLf^``>^R&Q002lON29eVZT@gB?6NZ@|5b_ ?eoH(6(G59`^4B`Vre+LUicE2!>c+iCzcrI?Ar)1Yg2U6s#NySVbDL zwWkhcj8t~5 C-shkt-M!jpTcc`&@ z?xHv#BPF4Ge#4q^k+FMcT(Rro^G!MzYhR M@(SfOrSYShvkuLrk0x4tAOwg$PS+UwyCZ@&puvBnDYkP1TD0 z!R(a`YW>e;^}u!hJ&FLw?MFR}|LGX}U$+VQ&vg9$`v*7ry1@Hn&;P>;;Q!|sUR(NB zFYzf9V5xpszW@$@-T7zrN?9SZPjTHmn &n1g6neU{_H?FweGnOAC-Jo^X|rU7Wfa*zH>*B#eF_)ajC z3dPhP%u{f=*?z#a!ub`!%`^E3IxFvxzqQNuyR0NS8cqTA#g$Xf$^>2B*F>ngxm*Tq zW%x3dD?%UO NfHX2?& W{u=HcTArvM2GhAOjoS2@}ORCHR`L z+oZzG7Uh;y944}@CiWdNlOn?>Ve8U}=JSuNq*5u7u!Cy?pDxOTK8g(nwEg*U>t)zd z^i+tN sU!uhv$!r{krf6#Hmq|s~kCFC9GsT7sboS{`C4h1t8Lbo{3qlQ-`P+#fyiFoj4; z+8;e$NWt#YU)h9*0c_)o7}ZO-Te<{NS)9eNa^I@gCiwh#m;ENmR1VI|__~6NlfRw} z{)4~bVrMiuD%K$sylK6g$L*|_=c@Cncb2H}@n(l%IVSqQqT#&l4OlFK3pIb7x)u18 zP^xRbi}Wr>ab#BE;BtQdHmPfKdC?P)p8AxMGJC04iHn|e(sXulJ`7E`Fhpy=IMLfH zu$Nc!PX=W~xD4bEwKi(HNS6=SrN)>IS2cvb7S5-a0=em5w+xx#^D-49FLErQ4OVxe z=E5oWtE+MsFu7Ji_m9XPheDR&&Ivv9qGRGqmqeFs-(*Ls#+w~TkUmNBU(H1h?QTBa zH)cMo9=LSrj@r>4vV~ %g_ih)drX;8?_0BEv+BBuGM|5Tq9zPOz;{?QNzw&+RJ?MZ(wNVKmyug z7H2GV6UI15UQ1Y&4>IMCGH*E?<7W96G~74wG{9FY=Gmt9MmS0hj^48ZTxqj|5vxSQ zE(EulpAP1KCz7F@#PqLPobc6=8xN?eFiG9_x2KoTo4uAYdGR#*>QEJj(-6n)3fa>4 ze6nQow^FrXa}W1F(mZEGAfik5Z~o_a9?kFF-170xolBOR6Q5=@XP5!33gOIZLR9gK z`Nm%<0L>h{w;b8uY~dcTs(lY(pz5-Eg0Ot6ILx-TVr|do{7W?#Yfn1 ~)780v0j<2pR_<((O`bVI2ShRXyFMWX79m+IzEj{fPALwc @Ip)k#hF&@f^)0++Ci|LK zX>!kBc>QAC{M%5$H%qsL^yrwMYsncXHmly*{D;|XHT$dM!;HfMT&bW}PdfnS-r_Ur zf|#vr^nOAtv$t@cz#{0qS$?e4?R3-~x-glbRdXMMz{TLH{B0hiGcLA5CTRc67-nhU zuWXVMf=Min^e1t5vQuQEWxZjtd>x?a(sM35PB;XI6&qo_mzr bx34Ml@WN`5iOAMp83szyJLex#m1?71=Uqm%p@Ut%!(h ze-%V#TyR oI$bzgdC0(tYJN+Jb&`;tUxb=#t+HK`SKA!mF2B7s)l#`qqnLe5 z`b<0Xe$Q{P`h-887RsOV-o(M`op~^S&rvF7QLW`Hxf%_ezc|PG(t_>=XB4u>3o5h{ z8?^Nr%aGkIs#>@!(R{P4@~=9`i!uw9p#SqClWRh{pufDQ)*Uek(S%>JN-aC{>{f=O z2{CI8$W!r;VRctP(MX7*`C1U;PVdu>hnoovU%tgMeX?l{=m$S_N!U$0TNEuj0ZCMb z*^~t^qC(9M`<@C-FpnQ6sq=LHirrla*^#v{ceeH`w*|T!4|^U+dtclwry#;KPq0!> zdjn)-QT9#t#a*vS7LN$KupCaHK#wMMXIVf!WL%q)M#;hAY5-3GVKCBBisX(LR4ed| zB$`z4JRZ`nv7)}5UABtEOJFVL*n56rT!K_my!66D#xxW!xY}4HJXTaB!4C6GpTNF1 zq;2jv((xx+H4h=RV#))?HEK&c2IN_GOvTuQ7ys;HZ~ER5{oR4kHUY@zqnf;vnS@fG zEoS*WTMNJ(Cw@YEAiNRA`WtX>B-~9y=(J|{l_i4#8?Q@gYBD8)p|7bN2Gic(SBO&D zt!6V%VKMFT=1rML0d$VGo;>Uq&~2pi6|r#Dy-ClyY5UK+oIhLvshtkdcT0BcR5o?T zR0TubB`TFoYF-#ZQ7})xQ)?-!W28Xi&Zu>|pdP}wCU>}8!3FJX-)mS)QJtO355&h^ zJYXMLJtydX7dnwO-^(9pM#bA=6zW~P2(4{MH)g+Cd8#v~%-#HB>3GFU_3rp3B7~ zb=L@oC0WZMoNWgdV96*GFr}h3m@L;T&;{b6^>|TleQyru!TX;Zf3Zkno6?I1cKZ$; zhs%Fu=+_M{lPSpIn0jKOs$_i%tm4k1`Aemd59H>oDZ8=Awi2GW89@yy@+avFyA#K;0hom>Lm*%F+2GyAe@i%c`UyJfQHPS3avW*c})TV#O9i-)Zn6H-Cj^K zePc?|>5rFhG!!Fv_kWDjeBg>UDcW@v=`WG U!#lt;Yd(25JMO{lIU$X=Zi@uR5=$!+-+ zpH1b|j&ERsY=v^Oe~(LR)aIU)tMSh?^6c6IAy+gO)75}A-?mCm?hQ;$QAW(R(D*`% z&L*@$p`lBN?$GWL%1URohex}ljqj9H?MudDy@|Jy;!KR1G}$jBQ67I|nLPh*AOLh& z$)MBCLhrok>A3Xc5k`U{DSf9pVa*~WvnOvA%u-;Mrl3v>(TBUW@*p$2KW=y^M^4$n z0ut^BBxq*Sp=Laob2L^?IS_GR2XErI#Q7i$iLrCmes{xYUyw`V)hkEg*ONPd?^eK& zqeoQTkwd$q%;)nJ)4uf0J3gManE+u?_djDsZ(2Uu5R@)IXN@NVy#&<(2P9ASOJArw z=6esv1>JCC(f(t!efOVD)zY1$BDFk~pU=-d{0hzNqd2f?v~h{teU~iV7d!RO{>+nT zS#F@nGJJf0Z^#(4@})$yN6kCu(88Z59|+~AG`a^%vfSeQX;$dCcO$LrRN~KbWr0@R zzv InnG^%DB& zJiO0;%sBhA+AxS_j1=P=C`jllq-Z~z0>r|s|Lt4d?HA*2WQMk-2QPx0W{^*+o8}7> z=J08C8p(X{c~(L<-{y89ppK6joysq+S 7Q {}f7SRR7W%WVux^)btN#Wy{|@Vl!K< zS?{IwufocK*-mZsUR|7ELOaYoF 7|ZS@iv}Gsg6rA4!kYA@Iu0iuzZl z7987mF0n1{6T$dspuoVZuJvC*IZ%8zAG}Oj=M{e##wE*NGS_MT1Pghr=xO#%(N~T- zJYn&yuW>OCOMhq}j_pM#riH7fb)X268Afx`hCen^@EQN0-R0)OcVTQY+htJt%gk@! OFgtn5sOW_2t^Wa$9d#Z6 literal 0 HcmV?d00001 diff --git a/tools/asm-differ/.gitrepo b/tools/asm-differ/.gitrepo index d3e4418c9e..241c0ad6b6 100644 --- a/tools/asm-differ/.gitrepo +++ b/tools/asm-differ/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/simonlindholm/asm-differ.git branch = main - commit = f30d43aceba6291aa3c08b7317b0458b6d734321 - parent = e977dfeb374c6de4076ca9a0f44ba5a6a701d849 + commit = 70c33cc125666495f84f8c7bee60d3158b4b1164 + parent = 62341d8db0c29d1d4f0c360196e428309ac373b6 method = merge cmdver = 0.4.3 diff --git a/tools/asm-differ/diff.py b/tools/asm-differ/diff.py index d7ac3f3440..2ec117a126 100755 --- a/tools/asm-differ/diff.py +++ b/tools/asm-differ/diff.py @@ -640,6 +640,7 @@ class Text: class TableMetadata: headers: Tuple[Text, ...] current_score: int + max_score: int previous_score: Optional[int] @@ -832,6 +833,7 @@ class JsonFormatter(Formatter): for h, name in zip(meta.headers, ("base", "current", "previous")) } output["current_score"] = meta.current_score + output["max_score"] = meta.max_score if meta.previous_score is not None: output["previous_score"] = meta.previous_score output_rows: List[Dict[str, Any]] = [] @@ -1184,8 +1186,9 @@ def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: assert len(symtab_sections) == 1 symtab = sections[symtab_sections[0]] - text_sections = [i for i in range(e_shnum) if sec_names[i] == b".text"] - assert len(text_sections) == 1 + text_sections = [i for i in range(e_shnum) if sec_names[i] == b".text" and sections[i].sh_size != 0] + if len(text_sections) != 1: + return [] text_section = text_sections[0] ret: List[Tuple[int, int, str]] = [] @@ -1327,11 +1330,23 @@ def dump_binary( (objdump_flags + flags2, project.myimg, None), ) +DATA_POOL_PLACEHOLDER = "DATA_POOL_PLACEHOLDER-OFFSET_{}" +DATA_POOL_PLACEHOLDER_PATTERN = re.compile( + r"DATA_POOL_PLACEHOLDER-OFFSET_([a-zA-z0-9]+)" +) -class DifferenceNormalizer: +# Example: "ldr r4, [pc, #56] ; (4c )" +ARM32_LOAD_POOL_PATTERN = r"(ldr\s+r([0-9]|1[0-3]),\s+\[pc,.*;\s*)(\([a-fA-F0-9]+.*\))" + + +# The base class is a no-op. +class AsmProcessor: def __init__(self, config: Config) -> None: self.config = config + def process_reloc(self, row: str, prev: str) -> str: + return prev + def normalize(self, mnemonic: str, row: str) -> str: """This should be called exactly once for each line.""" arch = self.config.arch @@ -1342,9 +1357,143 @@ class DifferenceNormalizer: def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: return row + + def post_process(self, lines: List["Line"]) -> None: + return -class DifferenceNormalizerAArch64(DifferenceNormalizer): +class AsmProcessorMIPS(AsmProcessor): + def process_reloc(self, row: str, prev: str) -> str: + arch = self.config.arch + if "R_MIPS_NONE" in row: + # GNU as emits no-op relocations immediately after real ones when + # assembling with -mabi=64. Return without trying to parse 'imm' as an + # integer. + return prev + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + if imm != "0": + # MIPS uses relocations with addends embedded in the code as immediates. + # If there is an immediate, show it as part of the relocation. Ideally + # we'd show this addend in both %lo/%hi, but annoyingly objdump's output + # doesn't include enough information to pair up %lo's and %hi's... + # TODO: handle unambiguous cases where all addends for a symbol are the + # same, or show "+???". + mnemonic = prev.split()[0] + if ( + mnemonic in arch.instructions_with_address_immediates + and not imm.startswith("0x") + ): + imm = "0x" + imm + repl += "+" + imm if int(imm, 0) > 0 else imm + if "R_MIPS_LO16" in row: + repl = f"%lo({repl})" + elif "R_MIPS_HI16" in row: + # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a + # correct addend for each, but objdump doesn't give us the order of + # the relocations, so we can't find the right LO16. :( + repl = f"%hi({repl})" + elif "R_MIPS_26" in row: + # Function calls + pass + elif "R_MIPS_PC16" in row: + # Branch to glabel. This gives confusing output, but there's not much + # we can do here. + pass + else: + assert False, f"unknown relocation type '{row}' for line '{prev}'" + return before + repl + after + + +class AsmProcessorPPC(AsmProcessor): + def process_reloc(self, row: str, prev: str) -> str: + arch = self.config.arch + assert any( + r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] + ), f"unknown relocation type '{row}' for line '{prev}'" + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + if "R_PPC_REL24" in row: + # function calls + pass + elif "R_PPC_ADDR16_HI" in row: + # absolute hi of addr + repl = f"{repl}@h" + elif "R_PPC_ADDR16_HA" in row: + # adjusted hi of addr + repl = f"{repl}@ha" + elif "R_PPC_ADDR16_LO" in row: + # lo of addr + repl = f"{repl}@l" + elif "R_PPC_ADDR16" in row: + # 16-bit absolute addr + if "+0x7" in repl: + # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) + # computations and are unimportant in a diff setting. + if int(repl.split("+")[1], 16) > 0x70000000: + repl = repl.split("+")[0] + elif "R_PPC_EMB_SDA21" in row: + # small data area + pass + return before + repl + after + + +class AsmProcessorARM32(AsmProcessor): + def process_reloc(self, row: str, prev: str) -> str: + arch = self.config.arch + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + return before + repl + after + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + if self.config.ignore_addr_diffs: + row = self._normalize_bl(mnemonic, row) + row = self._normalize_data_pool(row) + return row + + def _normalize_bl(self, mnemonic: str, row: str) -> str: + if mnemonic != "bl": + return row + + row, _ = split_off_address(row) + return row + " " + + def _normalize_data_pool(self, row: str) -> str: + pool_match = re.search(ARM32_LOAD_POOL_PATTERN, row) + if pool_match: + offset = pool_match.group(3).split(" ")[0][1:] + repl = DATA_POOL_PLACEHOLDER.format(offset) + return pool_match.group(1) + repl + return row + + def post_process(self, lines: List["Line"]) -> None: + lines_by_line_number = {} + for line in lines: + lines_by_line_number[line.line_num] = line + for line in lines: + reloc_match = re.search( + DATA_POOL_PLACEHOLDER_PATTERN, line.normalized_original + ) + if reloc_match is None: + continue + + # Get value at relocation + reloc = reloc_match.group(0) + line_number = re.search( + DATA_POOL_PLACEHOLDER_PATTERN, reloc).group(1) + line_original = lines_by_line_number[int(line_number, 16)].original + value = line_original.split()[1] + + # Replace relocation placeholder with value + replaced = re.sub( + DATA_POOL_PLACEHOLDER_PATTERN, + f"={value} ({line_number})", + line.normalized_original, + ) + line.original = replaced + + +class AsmProcessorAArch64(AsmProcessor): def __init__(self, config: Config) -> None: super().__init__(config) self._adrp_pair_registers: Set[str] = set() @@ -1394,23 +1543,6 @@ class DifferenceNormalizerAArch64(DifferenceNormalizer): return row -class DifferenceNormalizerARM32(DifferenceNormalizer): - def __init__(self, config: Config) -> None: - super().__init__(config) - - def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: - if self.config.ignore_addr_diffs: - row = self._normalize_bl(mnemonic, row) - return row - - def _normalize_bl(self, mnemonic: str, row: str) -> str: - if mnemonic != "bl": - return row - - row, _ = split_off_address(row) - return row + " " - - @dataclass class ArchSettings: name: str @@ -1420,14 +1552,15 @@ class ArchSettings: re_sprel: Pattern[str] re_large_imm: Pattern[str] re_imm: Pattern[str] + re_reloc: Pattern[str] branch_instructions: Set[str] instructions_with_address_immediates: Set[str] forbidden: Set[str] = field(default_factory=lambda: set(string.ascii_letters + "_")) arch_flags: List[str] = field(default_factory=list) branch_likely_instructions: Set[str] = field(default_factory=set) - difference_normalizer: Type[DifferenceNormalizer] = DifferenceNormalizer + proc: Type[AsmProcessor] = AsmProcessor big_endian: Optional[bool] = True - + delay_slot_instructions: Set[str] = field(default_factory=set) MIPS_BRANCH_LIKELY_INSTRUCTIONS = { "beql", @@ -1543,10 +1676,13 @@ MIPS_SETTINGS = ArchSettings( re_sprel=re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)"), re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), re_imm=re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)"), + re_reloc=re.compile(r"R_MIPS_"), arch_flags=["-m", "mips:4300"], branch_likely_instructions=MIPS_BRANCH_LIKELY_INSTRUCTIONS, branch_instructions=MIPS_BRANCH_INSTRUCTIONS, instructions_with_address_immediates=MIPS_BRANCH_INSTRUCTIONS.union({"jal", "j"}), + delay_slot_instructions=MIPS_BRANCH_INSTRUCTIONS.union({"j", "jal", "jr", "jalr"}), + proc=AsmProcessorMIPS, ) MIPSEL_SETTINGS = replace(MIPS_SETTINGS, name="mipsel", big_endian=False) @@ -1566,9 +1702,10 @@ ARM32_SETTINGS = ArchSettings( re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), re_imm=re.compile(r"(? Tuple[str, str, str]: return before, imm, after -def process_mips_reloc(row: str, prev: str, arch: ArchSettings) -> str: - if "R_MIPS_NONE" in row: - # GNU as emits no-op relocations immediately after real ones when - # assembling with -mabi=64. Return without trying to parse 'imm' as an - # integer. - return prev - before, imm, after = parse_relocated_line(prev) - repl = row.split()[-1] - if imm != "0": - # MIPS uses relocations with addends embedded in the code as immediates. - # If there is an immediate, show it as part of the relocation. Ideally - # we'd show this addend in both %lo/%hi, but annoyingly objdump's output - # doesn't include enough information to pair up %lo's and %hi's... - # TODO: handle unambiguous cases where all addends for a symbol are the - # same, or show "+???". - mnemonic = prev.split()[0] - if ( - mnemonic in arch.instructions_with_address_immediates - and not imm.startswith("0x") - ): - imm = "0x" + imm - repl += "+" + imm if int(imm, 0) > 0 else imm - if "R_MIPS_LO16" in row: - repl = f"%lo({repl})" - elif "R_MIPS_HI16" in row: - # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a - # correct addend for each, but objdump doesn't give us the order of - # the relocations, so we can't find the right LO16. :( - repl = f"%hi({repl})" - elif "R_MIPS_26" in row: - # Function calls - pass - elif "R_MIPS_PC16" in row: - # Branch to glabel. This gives confusing output, but there's not much - # we can do here. - pass - else: - assert False, f"unknown relocation type '{row}' for line '{prev}'" - return before + repl + after - - -def process_ppc_reloc(row: str, prev: str) -> str: - assert any( - r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] - ), f"unknown relocation type '{row}' for line '{prev}'" - before, imm, after = parse_relocated_line(prev) - repl = row.split()[-1] - if "R_PPC_REL24" in row: - # function calls - pass - elif "R_PPC_ADDR16_HI" in row: - # absolute hi of addr - repl = f"{repl}@h" - elif "R_PPC_ADDR16_HA" in row: - # adjusted hi of addr - repl = f"{repl}@ha" - elif "R_PPC_ADDR16_LO" in row: - # lo of addr - repl = f"{repl}@l" - elif "R_PPC_ADDR16" in row: - # 16-bit absolute addr - if "+0x7" in repl: - # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) - # computations and are unimportant in a diff setting. - if int(repl.split("+")[1], 16) > 0x70000000: - repl = repl.split("+")[0] - elif "R_PPC_EMB_SDA21" in row: - # small data area - pass - return before + repl + after - - -def process_arm_reloc(row: str, prev: str, arch: ArchSettings) -> str: - before, imm, after = parse_relocated_line(prev) - repl = row.split()[-1] - return before + repl + after - - def pad_mnemonic(line: str) -> str: if "\t" not in line: return line @@ -1741,7 +1803,7 @@ class Line: def process(dump: str, config: Config) -> List[Line]: arch = config.arch - normalizer = arch.difference_normalizer(config) + processor = arch.proc(config) skip_next = False source_lines = [] source_filename = None @@ -1783,7 +1845,7 @@ def process(dump: str, config: Config) -> List[Line]: ) break - if not re.match(r"^ +[0-9a-f]+:\t", row): + if not re.match(r"^\s+[0-9a-f]+:\s+", row): # This regex is conservative, and assumes the file path does not contain "weird" # characters like colons, tabs, or angle brackets. if re.match( @@ -1797,10 +1859,11 @@ def process(dump: str, config: Config) -> List[Line]: m_comment = re.search(arch.re_comment, row) comment = m_comment[0] if m_comment else None row = re.sub(arch.re_comment, "", row) + line_num_str = row.split(":")[0] row = row.rstrip() tabs = row.split("\t") row = "\t".join(tabs[2:]) - line_num = eval_line_num(tabs[0].strip()) + line_num = eval_line_num(line_num_str.strip()) if line_num in data_refs: refs = data_refs[line_num] @@ -1835,20 +1898,13 @@ def process(dump: str, config: Config) -> List[Line]: while i < len(lines): reloc_row = lines[i] - if "R_AARCH64_" in reloc_row: - # TODO: handle relocation - pass - elif "R_MIPS_" in reloc_row: - original = process_mips_reloc(reloc_row, original, arch) - elif "R_PPC_" in reloc_row: - original = process_ppc_reloc(reloc_row, original) - elif "R_ARM_" in reloc_row: - original = process_arm_reloc(reloc_row, original, arch) + if re.search(arch.re_reloc, reloc_row): + original = processor.process_reloc(reloc_row, original) else: break i += 1 - normalized_original = normalizer.normalize(mnemonic, original) + normalized_original = processor.normalize(mnemonic, original) scorable_line = normalized_original if not config.score_stack_differences: @@ -1904,6 +1960,7 @@ def process(dump: str, config: Config) -> List[Line]: elif stop_after_delay_slot: break + processor.post_process(output) return output @@ -2123,8 +2180,15 @@ class OutputLine: class Diff: lines: List[OutputLine] score: int + max_score: int +def trim_nops(lines: List[Line], arch: ArchSettings) -> List[Line]: + lines = lines[:] + while lines and lines[-1].mnemonic == "nop" and (len(lines) == 1 or lines[-2].mnemonic not in arch.delay_slot_instructions): + lines.pop() + return lines + def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: if config.show_source: import cxxfilt @@ -2152,8 +2216,12 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: btset.add(bt) sc(str(bt)) + lines1 = trim_nops(lines1, arch) + lines2 = trim_nops(lines2, arch) + diffed_lines = diff_lines(lines1, lines2, config.algorithm) score = score_diff_lines(diffed_lines, config) + max_score = len(lines1) * config.penalty_deletion line_num_base = -1 line_num_offset = 0 @@ -2385,7 +2453,7 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: ) output = output[config.skip_lines :] - return Diff(lines=output, score=score) + return Diff(lines=output, score=score, max_score=max_score) def chunk_diff_lines( @@ -2461,6 +2529,7 @@ def align_diffs( Text(f"{padding}PREVIOUS ({old_diff.score})"), ), current_score=new_diff.score, + max_score=new_diff.max_score, previous_score=old_diff.score, ) old_chunks = chunk_diff_lines(old_diff.lines) @@ -2504,6 +2573,7 @@ def align_diffs( Text(f"{padding}CURRENT ({new_diff.score})"), ), current_score=new_diff.score, + max_score=new_diff.max_score, previous_score=None, ) diff_lines = [(line, line) for line in new_diff.lines] diff --git a/tools/z64compress/.gitrepo b/tools/z64compress/.gitrepo index 9507f81ce9..905b330d9c 100644 --- a/tools/z64compress/.gitrepo +++ b/tools/z64compress/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/z64me/z64compress.git branch = main - commit = 98ef0ac25f7adb47235c839838e29496d3546917 - parent = 69cc19eea8c30e50e280f400e8cf06fe66efca60 + commit = ac5b1a0d0b23346362a68a107da3478227f8e703 + parent = 33e3aa92181a0543826adc10a5f3304d60b391dd method = merge cmdver = 0.4.3 diff --git a/tools/z64compress/src/main.c b/tools/z64compress/src/main.c index 71d1d20400..bd044059d1 100644 --- a/tools/z64compress/src/main.c +++ b/tools/z64compress/src/main.c @@ -221,7 +221,7 @@ wow_main } } - fprintf(printer, "welcome to z64compress 1.0.1 \n"); + fprintf(printer, "welcome to z64compress 1.0.2 \n"); if (argc <= 1) { @@ -356,7 +356,6 @@ wow_main ARG_ZERO_TEST(Ain , "--in" ); ARG_ZERO_TEST(Aout , "--out" ); - ARG_ZERO_TEST(Amb , "--mb" ); ARG_ZERO_TEST(Acodec, "--codec"); #undef ARG_ZERO_TEST diff --git a/tools/z64compress/src/rom.c b/tools/z64compress/src/rom.c index decf8ca1d0..008597b524 100644 --- a/tools/z64compress/src/rom.c +++ b/tools/z64compress/src/rom.c @@ -68,7 +68,9 @@ for (dma = rom->dma; (unsigned)(dma - rom->dma) < rom->dma_num; ++dma) #define PROGRESS_A_B (int)(dma - rom->dma), rom->dma_num -#define ALIGN16(x) (((x) + 0xF) & ~0xF) +#define ALIGN(x, n) (((x) + ((n)-1)) & ~((n)-1)) +#define ALIGN16(x) ALIGN(x, 16) +#define ALIGN8MB(x) ALIGN(x, 8 * 0x100000) /* * @@ -936,7 +938,7 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) cache = rom->cache; - if (compsz > rom->data_sz || mb <= 0) + if (compsz > rom->data_sz || mb < 0) die("invalid mb argument %d", mb); /* get encoding functions */ @@ -1057,31 +1059,13 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) /* sort by original start, ascending */ DMASORT(rom, sortfunc_dma_start_ascend); - if (matching) - { - /* fill the entire (compressed) rom space with 00010203...FF... - in order to match retail rom padding */ - unsigned char n = 0; // will intentionally overflow - for (unsigned int j = 0; j < compsz; j++, n++) - { - rom->data[j] = n; - } - } - else - { - /* zero the entire (compressed) rom space */ - memset(rom->data, 0, compsz); - } - - /* go through dma table, injecting compressed files */ + /* determine physical addresses for each segment */ comp_total = 0; DMA_FOR_EACH { - unsigned char *dst; char *fn = dma->compname; unsigned int sz; unsigned int sz16; - fprintf(printer, "\r""injecting file %d/%d: ", PROGRESS_A_B); if (dma->deleted) continue; @@ -1114,9 +1098,6 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) if (sz16 & 15) sz16 += 16 - (sz16 & 15); - /* put the files in */ - dst = rom->data + comp_total; - dma->Pstart = comp_total; if (dma->compress) { @@ -1130,26 +1111,66 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) dma->Pend = 0; comp_total += sz16; - if (dma->Pend > compsz) + if (mb != 0 && dma->Pend > compsz) die("ran out of compressed rom space"); + } + + /* adaptive final size */ + if (mb == 0) + compsz = ALIGN8MB(comp_total); + + if (matching) + { + /* fill the entire (compressed) rom space with 00010203...FF... + in order to match retail rom padding */ + unsigned char n = 0; /* will intentionally overflow */ + for (unsigned int j = 0; j < compsz; j++, n++) + { + rom->data[j] = n; + } + } + else + { + /* zero the entire (compressed) rom space */ + memset(rom->data, 0, compsz); + } + + /* inject compressed files */ + comp_total = 0; + DMA_FOR_EACH + { + unsigned char *dst; + char *fn = dma->compname; + unsigned int sz; + fprintf(printer, "\r""injecting file %d/%d: ", PROGRESS_A_B); + + if (dma->deleted) + continue; + + dst = rom->data + dma->Pstart; /* external cached file logic */ if (cache) { + /* skip entries that don't reference compressed files */ + if (!fn) + continue; + /* load file into rom at offset */ dst = file_load_into(cache_codec, fn, &sz, dst); } - /* otherwise, a simple memcpy */ else { memcpy(dst, dma->compbuf, dma->compSz); - if (matching) - { - // since matching rom padding is not zero but file padding is zero, - // fill file padding space with zeros - memset(dst + dma->compSz, 0, ALIGN16(dma->compSz) - dma->compSz); - } + sz = dma->compSz; + } + + if (matching) + { + /* since matching rom padding is not zero but file padding is zero, + fill file padding space with zeros */ + memset(dst + sz, 0, ALIGN16(sz) - sz); } } fprintf(printer, "\r""injecting file %d/%d: ", dma_num, dma_num);