From 3188a212b5ee339c7ee70bf484f40ce0e983b9dc Mon Sep 17 00:00:00 2001 From: Dylan McNamee Date: Mon, 28 Apr 2014 10:17:11 -0700 Subject: [PATCH 01/45] Fixing a type constraint solver bug submitted by Jaak Randmets, verified by Iavor. Regression tests passed. --- src/Cryptol/TypeCheck/Solve.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cryptol/TypeCheck/Solve.hs b/src/Cryptol/TypeCheck/Solve.hs index 604e2530..29eeb5ea 100644 --- a/src/Cryptol/TypeCheck/Solve.hs +++ b/src/Cryptol/TypeCheck/Solve.hs @@ -250,8 +250,8 @@ exportType ty = err = panic "exportType" [ "Unexpected type", show ty ] exportVar :: TVar -> Int -exportVar (TVFree x _ _ _) = x * x -exportVar (TVBound x _) = 2 * x + 1 +exportVar (TVFree x _ _ _) = 2 * x -- Free vars are even +exportVar (TVBound x _) = 2 * x + 1 -- Bound vars are odd -------------------------------------------------------------------------------- From 87042d4604314c65a120a2333a950c36201ba085 Mon Sep 17 00:00:00 2001 From: Dylan McNamee Date: Wed, 30 Apr 2014 11:37:15 -0700 Subject: [PATCH 02/45] fixing lone bad reference in doc, added syntax chapter, replaced Salsa spec PDF with pointer to it, fixed table in section 1.2.2 --- docs/ProgrammingCryptol.pdf | Bin 1454956 -> 1471046 bytes docs/ProgrammingCryptol/appendices/Syntax.tex | 387 ++++++++++++++++++ .../ProgrammingCryptol/appendices/grammar.tex | 11 +- .../crashCourse/CrashCourse.tex | 16 +- .../installation/Install.tex | 20 +- docs/ProgrammingCryptol/main/Cryptol.tex | 2 + examples/Salsa20.cry | 2 + examples/doc/Salsa20_spec.pdf | Bin 128071 -> 0 bytes 8 files changed, 415 insertions(+), 23 deletions(-) create mode 100644 docs/ProgrammingCryptol/appendices/Syntax.tex delete mode 100644 examples/doc/Salsa20_spec.pdf diff --git a/docs/ProgrammingCryptol.pdf b/docs/ProgrammingCryptol.pdf index 5dabd8f4a7bacbeb6964e24199a469ef5eb3dca7..5fb976cf4eafae3e25aee36d664bcd8fc5a21731 100644 GIT binary patch delta 184850 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

$P**J~2S`2;_ny;jB;tA)K% z$Xw*YE{9#alZ}u9BYPlhO~u0?i`oLRjN`)6J|5a@UJ`RmO%NpMFvMes!j3R{UkJv~ z24fXcGe$)vB%g)K$|5AL$n5k{J@td|%lTtBxHZl3TWn_JVkzXl??`M$n;0DA8*P#C z!jDhlj7%;%4s-RnGY<~_eW!CDjT73j!o0`NYA@NqzPDfl&hm_CrPs3yrv2XU(;F^2 z+C~^@c1KJWqm=D1n9;>|2<5jebV(+Ya)(2qo?xV-^1#Ig7`5%)*E(O43{15(af+_b z5s7=V_NEmKOH)!4M;aj!h7A<{vXH|fOm^?_S`hsepAlivh21gn25#j# zU3x_ix3LbakKw`Fz3T@JX6hjBR}g>7cVV$E0AnPBjZSbWwC4gm`RJ*VZy%u;79|yj z0QwHpnG=uqt)w)WpEEWUVH|%o>Y>Lb%lnOap~J(z*|O7Lo76_}LPO291ix(Fw1LRwuEIg8ucUEIX7Zvx&S8nyerCXJ{LGCL%NV2{ zmM+K0gg~V6D@J#bg9#-8hqa-i*0%^=MLc3XRUWtg-O-;~fRbw!f9n*~6`(bfv~=7uyHi$ewj~T-7B(+!sg!fkOtmpS-k@M9>g*YA}w_7+r+? zeT90_T?hN3Ht`jBw7t2BVall^cvDFHZ2d=%9|7RhOwL|*9dhrJReaz%VB;Juc=Jvw zKrv^4cGoG4EVN&Cp?9nr|I4luzC5KLe_&L(xps0)U{u_WgydPOZuVdl^Y##V;VEhh zv)H1{P?U_<9%*<-PFh|@apz%KZ7o7iap(gG{dWZd_c{w^{p{kC=9LBL+f`^7$!+Dl z2R|MY$wnmHnQxYa72!JTPL*jc*^566fjMXud&GYB4G7%qEGq!?N8|ZYtVB2$Mawno z>vqM}I3us%AL{+^+#4an=3akZq6t5O$WR(-^6jIPsS0pu9d22TkvXSKZEJpv{!s^B zq5C2`PLhJc_YfwbIO7jqLUuGf@y7GB3R=r2yqs|u^0_={_LB}A#=v2z8{;y10nIG; z5lw5;xmMuYYS9V^p6S3>@k5{dM26X9Y(Ik6X;@TSqQu?Zh2)B)j^{d=CKV1q*hvZ1 zB{qK%l!Hj6lB1O!PLYnV-WFz}VcKP^@Xc0CLO*!7+{Ss|YG()Lsab7Wxz{0dZU$4P z=x3fzLk<3gEWZ>%&;48@>9QRb z9HTpYc4EZ)4``r--V#UJ2}Fjqbm$0D?D*aT$f~1Ule|YdCwK+0M+ElZ#^T!rz0m#` zDZac174#vVqzDFAen|($JGZ{2LW&U}`qaIva zcUe@*);(iSHxWN!G~{)hPEw(|VF|hE?zl|0)H^2xN@Ckr6br zb?Al;*&&Ym0nNf8MnFzPy3XIq)?QKp7#mNYa_UwEH z8%Lhqnet(A5)frp_7a5dN>3tM@`oC)@MfzM=n)vLPo%1Wro1hT{HA+R;Jj{8(^W31 zd%cbSq0ZXh!#Cf8ZiIrX;|xbd=pM!~W2-!+UvlWLv|8oyZBCN$Tnco?u?1&$Q1hl- zhN11w&mpbiC@TN5Lij}#?s_&O6{ih3Tx9OvKs_`-ZDeNpXBDW&DI!k{z;)C8l)X$J zOX_is@B=#h?^;2MelOrF=vLub zBTn7dWnTyC`n=mdUoQ0@K3?`Wf8O`AX~Olt8ZwzQn6#J)rf-ns` zJnmhtN9jLHY+iBCXK~+`w^I2iCKO}tqoQm*3YdxH0@Ygz^a)INRWBGcqgIi=j%BqsCB|^pAh^*NWzvm4xb^sc{Pr<^X`NZJJ2zlY z>~mR>bI^V&=c;q~aX>9S4%K2ExGGtZ($)f?2l{(~xlE#zKQCUtvRe!d7sGzK3zol6 z#!P0Ze%={{Yoo1Vpsr+dDqpF1yY{Ou<#j|SEw!fHQ&k(@@?603Cr`{NF{elOCUu6z z)L!En7c|UJg-IP(Tmx9sPfF7)5|S#UrfaQpuV>WrZz%WelrLjlen`8++eY_vlxIDQ z07=3Y!5MMp`Fu52Rjp~^;^K|A%mYLWB*TpdDB`3$oneZntDE zSed~4VW>blF?V&CJ7Kn1Ip5G0x&>{ya5NUN6`T`HxWD}^q@B&n@xo@>rd z4`VEk&487I&H%dAxa;tPPo9)JwHYGrVp~Za9GrLJUHtMYrTSY4?--IUYYH0WbF5#b zRK6JF&Srz59V~saC;9|PNf(9+69X~$1||zB<2Y$rIv@T9`S(=W^n(RrwS;^(THCWB zDN|FgNEhsjlfUDne5FJpZMc711A(Sx(IS~p&~k!=-wX!Ph7SL5i8^NNOr$w`UZzHa zbCnB;&4^*5S{hkyU|DSvgH42oMraI~edk`Dgvy0vHax&0Cl^Lbywe%Ur2fV$i=YtEG5w;V4S`;{YO5Dq)4Kt z{8%}A`(0Y=7yX(F3R$L$jQ!%G)?`Qioloyc z&W&7!Zj?Fog^xD<$DWQr4{e~{+*HH^Z0PLv0Ox6%33s?l!wli~x+JRNRW(?r76thQ zKBNIn^J*%HTSwuzy+T5C{$e5(g4%fQnD8zGojucRtPN=cF^}MZ&}<5c1cI<&_&Dva zjLht%%|iDy<*B$E5<%dqVqrd|N=j5(ahn5V3Pf}ddfCXVx!0C1Rfr0_G`V8`Ek-tu zx!Q?iUW+|JUYJGVDG`{`dI-eIs(A$}BmK`zby`LfJFb@&ngE?e`6=TWfF>C^W=%|C z<{KC1AM4YK^OIKbWc2E+2f3rzSoXevXa>TE(X6aT1_GX(GE|^6g{p?LB7Pz?W?AxB zWA?2t`<2cd!o~{r_AzSRX$xOS3n4=scm(x?py6ue+_vZw4{dAcZ^Z-Xg_Gzp-Ni zE?qbEt$EW_@I}vz+Gf=MWFxP@0Zfzqx24OgXQ{XIdw$vt_(Vfw&GB%9_jXN#Kh$Ss znuC?~y%`O*=`Y|Zo7;LmMF#{YEPM*W5?Tc+E4)h{@(!V~l?$ZG|IymS2Q z7R~okZ3fECI`Z~pV0g`FPd4FYJdFoX)*YWKBYSnh66 zr(|}cU22+2ywa@iCw%BML~J_qbo8jLrWRLa-uI|;WyV|b_|Huean;O0~_`R}|q4&LXpSh82| zywMErj3&ry!d8-z6;lpKVE_H&prMtT5-6GFt|-blb?ZP&L%Yk6c>fX}V5wSPQMJN{ z76>fMSRWF1j5|<4aeziaIq2*$&k}>|G?8a)&TS%cF79fUte1-~$h+8V-_Sts`V|$F zK@cWzbj(Sxn)74$AuH2YHETX1{RGANVhM6tQ%sb=Hz(QVY~6TS`AaeZmPOj=F(Oq9 z?}iPiBp@p#bvl7$npdDlXY8E+m6kVoCjxZ5Jk}tA_kJH_U7(uDloeRT1G|?^`7Lh}RO&AST#7U+NY|39*IfenB& z%zq`}l+A1VfI#RJ&{JfplrRNEKy&IjpaU6kXAOX|Kmup zp*MmVj&nkp@DtNH7} z&;PM*2eJc5RuHn25y>E}pYkTNM#iGU;GK~F1n&`x^CUBPT8Y8tG?&S&010aFHp;2u zq-$Yifw;TWX8t{QM%ZXrjoxKrVO0z-xiC zJiK-~g?(5J-`06{ee3{xBSnXEX}r5(VnRsbGQ$%XR(a6j?j$P8QB7mEd~jBBu-t*^ z1NpH()ICqH+{1r|URZL_6=dk3a)SZsSWTj(Jl+{@JtHka+H{nps}JUXQSJ+d-wi4Y z^`Q)zw0UhfS%`(ZN2pTwZHui~0vgupmx0M}%_%7+#6>V$=I9l+wEHV&^sbsgyH(0H#XT95Gb%!}V{Z$NC z<*dX;6t)2_Dum{_t>tzUKm}uLi-)GT!i!NUf=3>q$B@bZ3DEr($0pZJr73rz#N$Z( zN}Zh+sEokqZWj_ylO~!Jo{Rt`q#A;!Hd+d7sR{ZZW17*VK2O{X9|V}E(ce*KAxPxp zW7B|Nx5dd5%(4<&kHEW+Iq z0!|Bd_)+?G`C|MD5!#tyT~sIf{l)<_izJdlWRot+G8=NmHC{9S&BL{l*+df%f$Z3z zmpLuMcm%@gFD6ysZXRo0;dY1{wJ7^c|6&={9bn)aKZNme`fDLLO~h_m5J>EO2MtL1 zX6XO5QT&PidH_eLoiT#Ad*}C1ilB`Ze29OwHe4^& zaVl>7TnXxsM;_{vpuf}okt4~d{aSwm^_>)r9NXH=&0(21A)csRX6SL_35yhP&libA znNliFI~>FRSxy<`#TRK?nJIQPHw^sP>31oT>tE!@p0F9Lw+Au{DKEv0zUv%5@-U&k zX#rfgW)k!Ab>I%9>>^D`wlz_H%^1ki#>13HrHfzbl7lkq6 z7a@%4;V-*S?2y)P3r~VG<9PTzq=o(RM`Daf=q=URggn54@--2`nvuv+IT!wEOZcmu8Qy4^{}(+>7WVO{vcH0v)~}S(i)ThMY_(!d=(YDfX%}Z zf_9ri=F@9J&eWsU;}@hUam?&YnWOYikc1VZ`~k9#Api&({B4JqZe=8T6-q_VlkMEv zPVp3gfl3C9`hyzK7Xct$xQVwpy>H&y?0z51Ae=q)lr3~MfI^*4XhoZY8wdG`CcmIN zYxQxcyVdC#^LR=8GQ2{2YZWyeo0c^&p4Su7P&^e4a;U$BYS0`JE+`DfSjflx5!9E1 z4j2_4I0{b-aI}rJT| z%)l%-5GN+)sYfQD@b~3C(fyYYq8aPbOoD#UTpfziW97d)i2jq_S(6PRmD`WPn;pJe z>w-WLv|P$A=&v6wMsiz7{4=SR-j&48Du|<4O z0<^*s<_Z=RgN^%K1VmP$((=dVX06BsYW=eEv(T?SkgOHMQZL%NU{K>e--yxOgprF9 zd{M+Uz8FIAo)&KNDpYa0u?|oqd^?7)$uVWsb1|yR?uv6Velp7X-WvH z1mg7%UesKEpas|wwhzT6HF6+l1*LBYA#ah}o}3}%71gyBT601hc=$T>%Indq$==WC zG@*k)G%y%$0JanW11=ENWa-dxH&&|!eYid6zV{KJWjBSVXuHlD%ozTY&kx2m4g(mj z4G?m}N2Hn?Ht{il{eB14u8;zu;<;pKV2&2UmfsF`1^PM{DsDlufob1EDI?^{IFMCB zZkOu!3$r&zRQ*}V*9C{jlc+~8r{e($@^e@T*VB6I{P$)w9G;i0!rho^9l0uv9a7-E zuv@$ejzaa&R46On*_6xZ(1T5dlQfVYqgT)z^&3+q%6u;>e5xuag|?Vb4ziwfi=zfO z_(w%~MJxfIeh+8;mtJm4V>Li8XHfFDo9b)Jbiw#WCChW#>hm|lrv&J5x_U=#aC{Zp<65p&y-HQgk)$LAs zB`Ol`?~paJ@U{&JHHre{~}*f_Y%7@Sc#6U3~2FxA_C2hu13X*LFi=G2Mqf&Q9sCr5N%B9|V@MX*A+qzwRlE zC4gQ_x54Xq6ZwlWI|oKlDHJvpI{RaAe|g4%2Mpx-ss!y}IAv@34MF&zew5t96TXss z(;&imokNZ~;=Kqc$iVzl$XF2M9tHYUwm6M>x*tb;5)-4-pCwX{%L;xY83gsvl zxJ^Gn^pCPb6@+P8s2XI^BFbzkQgAn9smA(_vl5f4>>nmu$eNq27XfA<c1>dS6n(at)fq<~R zkJRZ>GE)HPQfybQ1#3Nh!t=`{Br|Q#*D-J=Y#zi5^ z@ABlVoW%%8bF7^c-})rE9v~XZMO;bUY=|!H(W7*gaS`te1C#biFcRxUU%G&C#bWX;a+Sb4{qUq&SD6M5??;3<0avTyk2SQkU6?>d3Qy9+deT z6Y+nG9_B^(i7)j&`NYrjm8DeparU;^!S@{I^l+ZK>Jil8HaOIu#sag}qPClWl*DiN zCQ#pC&*W55JSRTgzBlre^5fe4t%PN;J2PLUwF=LC--(GFq!8I@aS{2m-okx`1v4!@ zIn%>?y$N`@9ol6gxud{o5W0_bRc|yjL_HJl793SfkKuQc&nl!Qv$M~E||W5un!mL_C3;-SJ_WIFh^|wgm$sF81gpq+|j+F5CMbH zNn5n%Y{0+2FBucW>2 zN(44E8AQ3~j*3LE06jidS&iNOL*@VlPbxu{iD zzn>W?Q>Y&GP<=VZf^2DlfMZ}8m`e9}8@|}Ff znO3euswAZ>7n7eDjInbAV;_ktO-(RR1m%x|av*k@$pY3+(b~=Snr%Sord^80i#Fg5 z3H0S(p+Z4v;_XlOI7`uGQ(8H*SLSsqzS5yn6VPKfae5?$o}Fkx#@dGAYsTfZuPT>w z?6Ej$%QsPk#i%I=PjB4yvTbUj9&gUizEa zvv}>w2_OCX`g#$%2(F(G6I;M8_xF`G*R76^oNmHj{A1qRrvNKY+|Tk}Dz%boSB=5Y z({XrI#NOSu4zA8Cs1*+y_Hx>)>M(Ge0|M~sSx!2b&n>V6;ne56KqD=GjvcsY=wAe4 z$az@Rll$k){9{ys7nU|>;nYM2wXr`9okjl+borYvI+BFHw*b}6z+fO zRHA?5>k3gIV8001m&BK{d9xCw8a9RKpW3&v83hgE%kIIR>udXUJ4z>5ioP#8RSFFj zDoV;x7s@agH*@osBsUV`OWgY(BM0LF6cmX6M8P)a{6UFKp5e^1Gm%^{($(g9Ip$%eAe&r{Vn)kcQ=H; z_!vI#_CKa>&YSl?yW3Bv2vZ1w&Y1I~kgm^eI-AopyxN;43K)!pF@nHZoby*8E`yrs z$pW1k?yT!_)ndu%`#a*!``Uwc$stq6=l%7oORmbC)vn*m%|Qc8>{R+}HiP!%nN`li z5Lv(DE5TH#8f)!gu0Zo>vU6)?-P=u^EQsp1RVl23Wl;oTd=Y-lgVv#HyL3!Eh@f zD}pg@B|!$65MrmrO^?z*6NG`Gr4H3ke2Vm)VD;$tZ*G&vteOvPuuG7D@u>x$ph+m0MH8Fqu(AY2mzRN6HikuYQ zdq{>Xn!Wjv=NtIbb3^7HQ12(+IvcOgY83M`6*WPkI!4;yt79B6G5CrrVtgO1s%63r z$6U9`ldqJ;Aa2dd(+mW&h$?yw;!I}o(#H&$XETrx z`|d&@skQ>ZM_G8{IZm|#4ta?B>Supm!D11da!kf;S~zW|r4nl#C?;!Qgf~q7eWb$5 zk^J!|SUH=w+-PCLMDsF7nt2zd0!6aQQ|xS6s;bQ-Q!Rt;mnXxi_9PXS5gM(nif%C> zHBzTQ^fpg(QW7io+PLP;^XduZbu#P2W<>s_J}tltF@2zB99D`KI0lPSo&Ne>_+j)D z^F-Z!wc%F{5go6!@dgu!=RCA;7?GZ+K;2o7*@|)e# zIIbU2cn;aYajS=Gnl<`SSy>zN>oE!j%Oz>Oq&51}U|3j?-5rUB*%3w|cTaAvm7aD6 zocJZ(Tyr)*5sb7l@SLj#yY<15DK0+pql;n-|NuvK=QWV0R)90eOQoCzit2=zT$FNS0AVKn`OP8YIs9wFiu z6lI}^Gdt^M04JeoBx;Rv{yKs7afkA~vZ5XSk)RwQ7ySo4u#qc`C-Y%TvTnfhMfd{N zX^7t?=R@^kqnS#|fU)x}lM;L8NLjmSrE|))#-eGB!u?FIS6^<_16Bf;Aglp zD+TqKjt3TQU{U!G+Cn*ji5-N1%t`BlE&`kyo*5q-ACoMNvW_JeA|V*dk_)RHuInD? zu4_<8mQ(WX-`r>EHTISvmteGDd{{=H-!%2O%=~I;YPnF9_4JDNMOL_Mhg;)E=zkyg z>`ZdrlHBqB;<=ILian5gOtUc1FsKfsnjR<*n8DQ=Z`{sv4jWuP z8$mi7EMg1i#yTobp&(H&6#DzP$VHOAc%zI;X8jmK;lq;W=%m0+!tBfh(d5P2=d!=4 z%)!gKx_XOishw6r_gqi?^+&O@gzA*ec+hqiiK>=uv;Yw433&SNihq zI4Cj51Z)c0l^C30pTREs-0UJrA3F7G?hq(-UrV5qbFf{OeUXb?<2yC<^xQF+cE@~7 z-qEd13xH%4f)t~$L0asVo`DI-HeLG*IeR2|?rHk>bDw>Vl|liF&1-fos7m^gU;VOm zg`><)@G4UX{gpq3MaJu$tM@K*@IAfRq-~)T3|L2f7RHSyZPs6kFl_z0Ty0DGhLOr- zU8NNcGn#2SV43ij%)^2*IMc89M{MdSXk6hBDU05&qlr+qDGZZV2l{%$7yQWC8r-Fb z@a@KyY~?G)!TXt47v9RH-d7j3t!7jRro{E*k13QN5ji23TY`d41Lu`_J-v~?uC;S4 zPk{uB_h%VM&!d_{^6)a#H@ap{MP%8AE?ZT1<0Td+#&(pYHI?}FO!ijNZ5)3Fv_h+w zi87B@Ic0*bZL9wrMuPc^#Gn;Xg*G=aBkEvd{m80sjJ(k-bg1haMF|7W0S7IvWT8S;I$JGw(eBb4^eOMLtP!k|!(k2%pxO4m(?X6V{Ts zzsSGQ5$?_CF}WU&p!(ZRR$8KDGOcr)tW;rM)Jvk|>~*yH+?fu*WNGeAAF6A|^(DvcHIe$o2%w>&_pwQ0!VJ z6X3Aeo+Es19zp^%487yQt-V2UR2orhn8rpGdRP#v)`@^`SA-v|qB1G9?x?6=(RRI` zs8m2^E)LHBGe0kAwnp!9AWyubzxWkvbiiVvV$~pWX}PiV3pbmrhH)9$qmQ>II9 z^E5F9Gjz>M7F!(T(MfW7Zt5YKBUJB@2JYAS$hvkH0fF*MIXPeRb0a*~7ue0NhNsF~ zcL1a8(8|{Rc$1Pw&z7SBYJ+CW#a1(D7dbsIXcA!SCa1Q|i*z;}I&aX7@Mu+E99JK1 zB~W>Ukx(4xoolgBw=a5j)~{xlkM3w*82b>$Av7BW@z#mNHKH!EFq*}e zwSlxL2HaR18qUKy#gX2EDOu%@!=HT^C@oTKvy*HD4x@#uq{pTJJ=l#zJ2}PNCTS+5 zB1h#LC=;nq8%NA8m!ZdCT#b~)hSnBgU!|@y&;8EMg$37!P^ic1F6zMD;|hK(Vd56u z9t|%?)M&VlsyVQHJcQZHB?d4J;`@fuzgQCq9NC6IMhAmWv1-V>ml0nk6Gc97@5!Rg zQi8&n7914nK`wUzg;W!aa^^w=)STJL`wk#xsysC{lHI&g3<{A9$>#{2nvQc_d4Rql@oQB2MRwaJpbeYae`Q;J?rf!yvl~ zhxi7~zFWhzn0e{4yyq~Wajjlb&CDh6M9#&*0$Mw_m=poL9yW|U=9(O*-=nqUh1*}}vkEZkXk z!CAv^6s;p-mX<*Jkew?D9>6F|&~z#ET-u1w5{eaj4CJoD#RNZ@jQ5h(Ybv7GyazPK zL*On{$g$R&fZ?mzYtMchf5%D8fUTK*T$p>Duh-^(_w{JgA^!7yUU^x>v`ebJ7t9lX zNmi3ogv)~ReN(P0`bEFnv>jVFCxIvv1OXbMQk13!E)*OR0Mv?sq9~hqy$OxK@Xhpw9GTY+f40Zoh_?AdoVcWx~I~ zh&5j!kAI`G0vrlmy`6CECt##EuYZhBe z9L9#JS~;mrm*wiNv>U6n3tgc9cgeZ!=xzRW(soRMJ<{$&n_N%~XDCP&|1VQd=RlC}BrA|G zcbyZ0s4NJwew$<6B$5-tJ0<6;+S=QnV+(`Y>u#(Yo(s9RBoMWd$^N;m6hf%*80kaf zL%r)qeL$1U7*(RVyR>Z5?M(fcfjjlQjtSbrwnUBH03i&95j-k*T~IJlp=eVnQY<|a zf>pcn_@7GE5!IC{$8bXQLDu`|%6A}NHh+@!Gpm)A*{!%Y)V)h~UAo-R)I6v~qw4qo zH?dV<)C=CkEg}XYuSWE;N9~sh0{^$QkaIurc+wRf(=pq|-Ga-wCzaJ+pTb;)b z+ss*LY}$XrfyY1PpSn+0I%M104Qx0)cje|%j`Sd^G^+7$u*yQ$~9!AN0{HO(#Mz6ne3OwZ$g34Q$0V5EFAEj_k?cibWIBXG8vzVH7E&E_iE3HSuMVlzYj3*u^-m8AkqO-cm)d%|4L`o4L zvlC3N>4_gfwxyNOAQgRuX~5vNLkEt9ioc#%fHCd9i|38}@UFV%i4iahw$^bu_|y_& z#HO{_deCmz=4abZA^ZTZHdRdf&!h{^#QNWySRfNK^Z#$wm5EvBKTH*Gi-cPaif3ATxujwhlYUcq>g4kYy@+BK#pd476Ak z7;4|z`TTTR%K3Uj0y?8EYgC(fwEmD^om`Zc|7%)ZlCmTSRFr0%k_YnWfM(|qY47q= z?eFG5MXNCgupIx6E}ziKku7%Dip{bwpn_KYNUvzTN6Fc1vzfG)$Je zggK+9E!AtYEG4o}s0V;r#%k2xX)U>dr!^-lJUi$dHT{NYR}xaixG~Umk6#F_P)L68 z)PooezJM%&sbr%1wFg|d#4WIsIJb4$Jf=l5BO$WjayfGdQRQ3L=$g2jQ|hrLQB#R@ zB=1b%VFkjTd){?t_lWPj!{KOJV?;Dpr)6gItYr1fd`?4=?!xFk$Gpv4=nI0|bTJ?Yt=EyaV z2xnsZJGj1eZh-2SYg@V;ly_^So2{eneCmSiCIhhi#S(1%T--4D13 z#XbHz9*@lqPFLx3tjnGkGDWDN+5F9MTAwY0~7ET_tT1Vk+tWj?#;1urD)e}>0Pu!Hp+!vVtO|=x? zkvkn7!e0@dmP@dH43$odrR4&jbRhBq92=4N)352utO#T~Fj@*RZ}oz4=4`-{ecBQN zNB(KjG6-C*BsTEWGtMB9MKTnZXC_~AR;jAHi#;I!3pA8R$H5K7m^9J!vc1Y~AOGlW z4A???u{7F)xFBUV{mv2Hi%n_*DKWJ$Djyakk;8!H!Ad6K`nuL}RRR8rn?_?;P?LI# zNd4^e{Zqba0{2wt@jlk*nMMbNK*c0op=p|Y@-P;)P#Szjx%gnvwOE;ko47eGS)ldDwSBo&HV|b_jaT*=rMreST+-NQYy=9k}N{A;( zp$~_TNnIP$ddqC}ttN?=w+cnwdaZzN8_JCHYom>jy_jFd440lYfs8Q71!>rq(29BP zzF-CB6pZmQtc8uFmuBM#miluJsy8pI`RlP`F?n_x3H@cu`Reo_n^8%<@wVk>S6FX_ zYu!XeFB7k9VjI8;;TJ1pzyQpbm+4w&%{Us#p&;Gu>BvJ-MCgs#sXN&GBE-uXDcEHx z$;O0O&pH7DFeIX3d`{avl>L1X32DRto|n1gkIKX+2kninw)9RD=_l^vf#NOLGmm!# zK#nxlRB?xusdaP`TvdQ1rE6q*@xm-`No$MLjB9$VAp~e+VV;tt)H&XM@GUe&nTnQy zX?FCRluWi>DxqBn%E+PiozNslL3Yg<1}~FOsI25wG1t z(+QVU#s}=zH;XTwit>rN$%+z5-BcxwdD9i(%gseW6|v>bG}XQ}K-w{aumBU!F$<67 zhV#0MAnicJ1=Zoo@#j5*yGEHZi%gS@E6eI2_)0tNTe& zI$~*#ettaU8*3Ud$HSnkl<%tzqn{mmVT)5&jxGdTi=D10PEUI}&R`X&?E29`$#?a#LvZUWLzseM5YR?O=qKviStxEaubpXeM z!ueg*RXmM9aaNUmC!4R8R@R>N9%Q$55qCG#7H2>{oD$iRFq_X0a;I9JgI8Z~dA)uj z5b(0*YUoi&_}gFbPk&umj5Yb=i3`BOZrlYm|JF^PmXRjVkxgnQZY~i5-M!zcZ)S~u z+FktXUEIXMRj6-A;-L1_^xe3qC=B4rGVWSUS9f_T{yxR-75d7RCFpc0u|#{C-bLcX zWg45yDUn~Egz?Jw)G`BVJ0g{&YP~VDbbZ1>xNS4_Gq8E8DZQ90%lvsd+luUmyBFcT zVmW8hbm;JP&1F^koCu+2ra~(__V3h4OeamK<=>7pRYsn&b?2evzKB@DVRwL2342qw zbAe$BS#2#V9B!;nB~$Vz@_0O^eU&z3)sFtsuM-e&m&QjyCyWnISm}3~Owb2YIM_&m zQUG`ILZ8;B)U^axt&!AkBiN^cqDTT_Ao;$$gl1UGUS-P(@@WVnec95JcC|lHDtb6Z zVOZW36K-?pl=r1Mp%dy5Bhu#JxC!jpOw%sFu-8``g(vU;z5&nXi_yX>tY<1_!Ul5g_*U2DL zn%2ygn)fkNv32y?Y?aWQW6UMti)=(hgXQzjQYX@r*iw1PxfE!r*f4^-0Q9P%qJkri zSiheQmZ8HZk*ASTWraY-qrs@)Y*%DDpZ7gjv-hLSavDo4oY$9>d0!71JQpU8q}ukr z3MkGxxk(bYnx$k}S(M_Mpc@m2f6}aCtlK@1jdG z`i(;lG_pI|$ij%~4}(gUx_?`sV&oy)I2dOK5vi-m6Fk-=9{d6f&!v5_jOj!0R1tDN zd#NV@@SSs3QD-u5N(>k1q4b3gW z))@!&F}917lZQZSU!4@_HIL#cMUrDiI=v>5DYAo_qG8*<; zO2WwsAusU(vEKPU`I@O7x)%ZD%6^676T!lNr9H7|5Zxlhg85gqDsFwJ8d5Y$pZSGM zwN!Q7QKwg9qJjsD!GszGDqzS~v>|+VdoJMf#zi`RoV);pkHz^MKo9fSbHFdUP*KuPNgaAPA9tjUQk5>m&^pca`6n{QTN8YDU2)F==8>t?5le)h_o&M!&> zXc{%n6j>+-s}SQ&SrR@>%BvMkzI4NaWymc|O8YwweO$e|_e5Lf6^dQF7T>9hKw-OE zczr1xwj2PhQXw@>Z+T>*leVPz@?g9b=JI0vH1;DS?GYSoGVhCTHv;W6Hs(-K`q(Vt zK);_6jiXL%=GZW-w-4SZ9R@$7wt)xn&M>T#en!A+8}_Y)K&WvXE??BoZp2SA)Ep@` zMw4zMIxyL02U;OQ?Ew!s!rn1lusWtYjAJzgn^sr1}d3*U(L=5dm zCsqKzHr!NL4|HphQ&R%z-lYzM3bpkL3M-!__cTo=)%_$(Y6Qx#fkh`urgUS_tuUF5a48Pu<9Or)VSzx>4HlwV<7y8ZUYl?dI4EvA@;O+B+Ji28 zeWE(Iff4s{y*cZ^N1nRWAp&7yZn^fDi?SArWmMMm9SCk>;3e$5Et&dAQtdK zSj01O$WrAx=h#xTC#GB|i2bZQ8K_W?q0~l87Wq_>E{2cCIt)7e~v z%a|w4{*)sjQJ|QH!;3$Qn~ua9k^_04hq-N|q?n66A@hQU!8W5vdh=*&#LfnCX~(_L zD{8SAOj;lcI_5`;{-clP(?@{-!14dOE=REIBX@)dZeQk4pTAid32;p(wKfc;a_##9_ER^t%}hN# zZ(S#Qrf1o{2?BGCq57L=y1QV@H(e-g<|kXy*2DfkW_fm2GySlapP+n;cHKLoJ1=XU z4w+K;0OpsBHIA=pJlOoWB+|iu4op#JAT^B=)Q$`vyKfuVP6xwaHJRr}OtP|b*2!#N zr5kUDX=D`W-crN1pT?%dDhfi`zrNfq-`^JZ8Wh{!(kE7s4P=#ym#q+ZYk7BJ7QWO3 z%am9XVND3drMtNap01H@Bp*Nt!V*Ne^kLJU0^1zZ+&CI-YlQa=T0+b;a-;^up{I*4 ztqiMm<3XXtOjJ`l%db~d#4XrvyDI*AkQKyURZ!haaOW>c+atkS&$g-54C*!D@AC z-G(b}Hmuqb&5Sp+Lrr|_plh?Q4CV7sOvR-#B@$oOjG+zA3DyH__zPL)u zfOwR);Rja~t-Y?Hi2Y4-x9Z{<7I-NJdrLCf`G&mgNC(&NaG`L>@A2lw$IJ;eF z%nT7ua7n{K(=9VD#CqN+t3lHyq2axgPq>4L7WQ7A>rUW}c*5+l%pb3y{WKK;ts+=I&Hr)wyt zpq**Fr2mG8#4j-%XA{$&EfuUUGaPLy`}cHNB;jG;O<>wqx*-)JZLn4yBXeG9!H!1H z-_!4B;=MnLEHcHRM5-oy{8MDT`j>;*>^b#aa|Dh~-G!H0wV>hb33%!=T3kL_7eKYJ z%%^uv`F19hran>Va{@j#^=3$mmy<;5G|POY->2Dyn#u1s^m@@ zHz?PPjlC^@Mt88N*Tu^|GKHq^Ol7h{Uj%TTjjUD*%O|rULyOS%l|$ACIXsp*?Fwff^!^Z&62+kkpRXmI zQb>bblL;$l3UXqt++Dewd33}ytUtZ&8gNH?bm=qAsK4X&-gcO@J_Uq||EJf)sy;o+R_?q7yK*u(84|S+VN8)Ud3=-V>}P zB)CPpIvt8Zs7c>yX%Qv&41$!!JH80KXb<{cG3%9Xt}HL0fF-hASSDU^NhKP9-@a|3 z?BIsm)k&w?Rzg@~r`qdn;;-Vs2=_CY^{{+QSt0SPwJ85^C>2=hu1K2?IA}|Ok<@f3 z=(Y*Gjs42ohk7(DX~ZVUduca6qd20Lu7RWh$>t8E4n*0!)zhQT={SR(DD+w%|${7 zwh#{WTN(i_8v^h{GM3|f?M2(NtE--zQC4=Cd%Ya%!t&={M9}Z|YL2mx0i{MOV*g97 z@s`#>uU(0%*IMVm?41mA{4&2!E<6uAWYGqY+zecv=`&IO@?~s>9APB{1+8GTw-FeG zlpM9HNhHg)3o@fgQ*L(ec7fQDSmH(`%RKP|-@g!oKkNa7nJmpC^mHb@3$u%NO*IlA z0YOhli zg=~p#y0Im+4cV>5Bz79Lu;(+U9VW45b-#94Hl>l~*{NlgVnbAvx+Q6^k`}u6HevtU z8-2Wm?ccyclO=gUL9C*3nS*ZLI|$anZK?&oT9A3x>lXHqvfGV%icNuinu&bL`&Bl` z0&HoEu}984mPP@Px5}8yGA7qc10~ymebbdp|a}Mat6vmiWvYlE3c?-P`wojhdQ2_X@rw| z75@8zizsE|ShHsj$JAEvbo{<*YK=wX_XMGHd3GNL|9IdN1OM}8?qV>KopF^?y#r%M zv+{V7)s{SJ3VU~Kvi>GRB}Ik_(~W*Jf9eJ)7M7=wV213w^sJ;SSY~W(L+3ny(Or`7 z-*4?)?_Q$2EuWQ0n74kZ2amWFdG~}x4Pe)&$ugl}o7OPhoNVEundR%^YW>u8F2b06 zy^VvQU4LA)-11u3s0op1Vg#Ip_q|z?kv9fHXfD$n-e71zPtN~lN&bzCd~nDBEbLtW z$sE(HH5N<3h17NUPh8|{H#C$BTs+jU-9Mu7%bQM&8U4A}&k5_^@yV-p6yHGf-W@s& z$^+zDI}r{LA*1iYP&q(yoBu)Q*7bW&*(q0nsg%eI-7jwZ-y^aP17*x;#f!Ihq*vh& zV;KGnW& z>W7-8{@$cJr)s(KZq*m3Dt4o9UGm5dK=(VNzgLgLLMeC4kED6a%6pwAb*925s{Cqg zYP}yi3uYTw*AAnbnqz4j||kG zm245JjtvVcX;3?^BajPpq{#%+*OC$wbm3b-+Xk2az5Gpk#~Pf3EVBTlhxG6X z7i@dczD+H?M6(}0OeT>X+mGdnOm~R*5%p(gsx{CklaiN}#l}ya<`-U2CEsNg3NeBbf zI%yxuL3TJjak_Zd^{X*DDCTSUBA-c19G=f)_a#%3Ec`Bd8dS1h?Va&8wtFtz^)I;N zVB8svs)eRxqpMZrNN{X1U~UoZ&iWh$Qj{KaR}*f(<-tjk$ zK1iy+CMg{ZA@@LG4J6Dk?DcB=F`8?H@num<07^aAU}>T8c~53Pez&X1#%%M9`);PV zQvEI zrssRc#3_8~0weO-zII2Anfv0bA46t7SKyz)C)A+%Rkel(k@zR{V)}$;mBmL4b9mn# zAyt4N-?@6+L=(d!+(J@BkXRg0Ztef$HJiG|Vz8Ewg1uT~I9QfSxiHruak;C7swWgq zPOrf8%5HL?%A|}7%h-!1AouClNr&+UIae7RH{8Cu|M`tJ@6WaU#Vt3@C#_qEIXP5C zH>53#tA38;Kk1f!M{Z|>CoqcNeNMq|Jic)~+%@%&(ae>8kw5L;o)3itdP<2_AL>}m zt}3dLy4_lVraan$Me(d=nKWGOw+1y^yf@CL4#~P{0a$mZj{%+mlk|->=^OO&NI%& zAvC*NPFAmkgylAP7wygiSVjUBr+#_kI>zV471Po4%Czx?JNdSt5K+dZ-89_Dc)Mh$ zReqtz(DbM{yI?NxkEgSyE*q2z4b zhkd9}?KXqfOaD%KIJUOtH1~CV^!2(QctHiT{+}YWUmxOw04EFQfBewqXmrP}F(Yk0 zV|fRblZ(HChlWGg1=@#u)A8#1b~AQwm;UrGNmu(kpuiaZIp0e8vyeX)_U#Yhj2Cch zW*|mezu}JI{Y`gxs`2Szfrcz*rhK3e$}$s|(S^gb&QBzlKGU@K?};5@Lf!M{1`xT)$9 z{m2&-?jDbqUIcLae-vYOs30a)24?Okg$32io}oln>yzzp{?6nD3IFC24Iwc;+`>PI zc=EOO^$=h}=pymvo3Y9M6nA1#WT7w%AfM*mKeGvTg)!X(D)8H@mut}8p|=XSkr)!1 zRP%rwkEk5Q0aHD4L|CU`!=sG)!6^J|eDCTr@cLjFEy2-}jh^u}%-+iKkuY~M&uIx5 z(UCqvTL0WV8c9Zyv5b0Na2*_&O>=4K#fhtR+|4$piwb@71TM=T*x5sBaZUg*ihdGr zAJhJAXubK^l&}ND41sVa8oXgWT#8=r@XBWeal!snTKdb+mM11PMup=(OKi6N(9Y$_ zue$b9rOVg{MTGuGocJc$J>--|wOmA<3>0O&Nl19E0Nx-*&S@&OjppYWWF&BFTKx z8W>}tbG_DC;ungbyV;pDc@tRRVO%0U_6vdlA-if5p>2R*|+RcDfhfU)8_d{2F?* zMetx1rIUQ~E%!WReyJdBfYQL+pRJM#sD(nSM2saBe1SEiM~V73ACuPNg^2>e{4b?C zH6#l+M;ZejCNYpDQx1tiRJS6ex@vxBr7Lr~nR(yL8V^zN^ zr)$lCSU7Af0=9~cQ4%N8aJUGTE#n2VZ8w166lRQpB6HSW$6=pO zq2u|Dqn|(%MWsR2sqPl=fA?YD8#5LaM-9`f84xp&A}4U$K(=t4J!kULUu6A)E_yi@3`w~!qb z$xDedmVY?j{||b#>i^fP)c-qP&99bEyP`zK zgk<^X$q7Wmqy^_}twO_OgGB)iS?3s_zg+bLBrgr=|83%b(b)fgn%GoM?|St6*6BM9 z+5Z5S;=h60A@Tv-Qv+g=58(3s4{%MuK7dQyw*9{V_f_{_;1c}{T!jzdW;Ip+KPK9$ zrgqJWSk;bh`Jc|e|9PSQ2e|tG1Kj6-fP4P?1Gu~&z^!EfxqJW@_y8_gzrFm%e@~z1 zpT4LsAJb=TWyZ&}M?wq_1)lo>FtRl&&3`}nAoKZG@pq3B^Cu*<5oE23XKNV^W)uXJ zmXH!(DA|zns>HFXc1I1Ex>q=EbLeTJ#sHlS`VuJ%y4k=u6&y zSQa6L^IlfKw8-Wv8o-Pym%#ph8O>E57l5@5CY3fJo{)u96xPEUU1K9y6m8p-hlE)G zFN)97e++N7XX}vgLlZw7!*puPdOGlkTZa7glOMb05L}IF3K7%h?`)5ABVEr)t=w}Y1OOnejq=bO`O_6F6T=VeC$4PD7=54Si$iu`JQ zI21!-T(zwU<lWb5Y|CdEqcj zS8*J2EF@;?;dd5oFJGLHQmMasDtZNz4a~{_86&_?vc-$&9h4E;l)>*}cB`eyiF@rg znX-Z03hm_6dn=0=zP}c^)@-4xRr45k>{xZn@>qqylcPgBldb+_$(!PO7p(c?!Ln9{ zQgl+GNGF(|e((uIjp#m_Y*bO;9{g}*N93dDt+M9uxSzsU)ZAdNv+8R0N?p}6d;P55 zn+!ma@1}35Ld?7>U`Cwr5fzfD`up|5y%ZQX-G@R!uFb=cz@eiUH-N;TbJFSiSu(iU z$?pin-D3DUsN=@;%uLpG>*XBBvl&uFPK6>&I=0ij<)vEEcx=!Y(9%Sm2;h0Z?RxZ| zn8V-?YZI24wI~YCZ11KYitWOB<1A_su7DYR4a%h29Yfz%yEdEm-0x7t8nZ_x7tbuz zhBn6|Dq-!vPD=)|Hc5Wdk3LNmBfWZrRVT8Vu3z>fvo`h2B!3!B-fmXxpD~-+LZaV* zq90M)KC6pxLAf4p!&)#6_=Azz_?7~BFRd$DX0T`A1=5Roaz&S|-j>rSCBYi^{m?hH z#FoP|t+{l^$e}OZq;T}UgTW-M(lr}5WaWZ))5Z)~Ow#o4{YQ-{Jt;R7fd|y4L!HEb8Yi(ncKbFI4#7WU zU5HTslt%m)T^Bd!e@aN7>B+|t4JCBnXwF-03)s4wfAuG5!EK>rfCBZ#H~F9=87(9^2sW;LfsFiSQKm0nUcUSKo9)6gNv)X|bVo&%a)$2j@`HBx! z8{+3!JU>t?mKt`!iS<_FU(T^hIQ0m`Pe+KsVS88;1bJ^JhNGq2Cx0p)_c=jxjuMOV zhf~Kw#{Uh==^7tUi6atlIl+RAaaIPA2rA>0vbX#!B!#V%)<@Q5Ee&HB^b-pf+h9f* zHW@8n!(?Z|ZCY)`@+fO)Vg*%OL~YEo%Pio4`!|{}|8NWh#zT)XBB5Et$Zjamj>1y3 z#nAdGH5wO3==bU+CU+`7JRNVbaNnh=N?evK$uztnwj+B&XbB39W*5F|Fk>AwNY+#u zGG!&{RXv=DKnhy2A(5~dna7(ST4L|prJAgm!snkUpDg}1YzOLIHv9?9rqd6?IVb0^ zTqyAT3$uF=j=?P_7*ZhBQa@h+ST-BnC>ihSK)`liYeq23!>Fzm>om&_0%DS3vlKqgDPwX_-Y!a6aAO))K{69puSiUvp^wF^%Q zS1cn#Yd_DQ$Yi0rsTZn8HLCDvt+e zOVUR$6L_B?Y6u=3G8WsNeer12rd-NMjzA3+a-QT6l`jN=L|>Y|_x^2C(Vd}Z&kK`C z(l#OI@ug6UFgCes_Pm4Wz#-e}0#Bu!zcOQPHB!s?by~XDecPNt7W8O&a0TPr^vnfT z-E4_ALC@uo&vR#e56qJJXzQ?Btzwoi2&Y0tnsS-4(|MsD6!9cN2BtCJAwx3YQ56;h zL)d4KKy|?0LGK2bkNVQjS(&QB>&lk*k_7L<9r!VScBIZEOxjZD+l^SOCF;V$U=Atfr*@sSDuxC0xU*Feiz8aF1Z)Qv{XV* zgtWpc3FTCzvF0RDD_ZG#RLmE5$PP*Ff5oT%B#RN+68$$=m!J{s84T8JPuS=4fn^*x zL#puwbY?qc9j4{T8dv~7o)iZgMAO&G_i!QpHQ$>%^VY|k+uQ92+nd|dLrML&!?&k! zi>w|ZdnS+8Er3-H4ggvnPg}tm;uiBB*O#*1xqV$cTOL6k9-U`SK%4u>agX5hTwbo- z&pyDnk|103{!f%1Nl#CCKMl-}LZVmORDRx9X zRxt{t=tZ~}H~h57uYaGG?5Akb-TGGR_Uf7+W)0I8z(wI(%kllbb*=yIbmMAmVoUJZ z_-5jKX!l@D6PL*yAnti%_4iw;m~z`Zgerv%L)tMev_n8yq?L5iF2MvpLjq^{p^Eq% z9gvP?&_NC!CyI+Jnnn!CAits<&i=h9>=Vd^Wl#_a4G05&I5hS*43KeOZNdcKMgnIR zo7@Qn62QKr&yxDL%+DE=N<$v8Q1_zlnP)Rvs_ntkjee$D8Htu0CMGg5OD}~GD*n_4 zLy=*Wg?4z0_c>zMH`><)|4J;#s=<(+J?$62r+heGF)FxoSirXhvzto_@cIhbdQ8~A zAz&~qGnJJ%eH=*8aBz5u^WOtJ!yqFf3V!Ve0+1>{#60hkyjhd%SG8*oT@RG;53J3= zkF=V#EoakRg=iF$^fDWnjQP=v$q~XH>-kCTi7}9Q*8#I?Tip%IQU4NkK-YFM&DN{^s* z0?QWs*BMvwWQ;7z9i*t}y3bEF+*6^>RI6@pGOH!nSB4t{8n4u?Rd>4svr1^H8FgsTQPm0OY(D&V%`?${0dQwyV#Dw%?i92#Q2<&) z)C5|9Ib9D19L0zPH=;H+NeLAS3ec71qx>VL|7ej4pX+Lt&qxco!%Y8o9eM>S{dYyC zE@5M&LWk4OfFVy7ew<4@##F`)11a%4MpolM4*DuQ++ytMxMkYmOAqUw46FD z*k}d$?MZIDIzqvmypvVyoo6AH(<34y?QMY>ritK$ka2FdH zMG!spRn0_SmkW`PVtOaSjeq5ZKS2>6D!EC0!tV%HU!0KI5t$?Dg?$aaBDNq?PH82D zK-oWg2wkZYm#?Z_ZxuMCWAsA~_`fZYlr~$vGPKX;WsY|&Z%n8%7EoC*!n@HpKE_KL3l^*BMcYg& zt>-f_PN@>loM>0ZO-p&TAvI&ROhyUQ233(e+tEiv>NZAwtwa8vVguOe)$=^|QF_9& zlQm1$dD6vB)te)lG)j#4ZK`d*Sd_?#XH-QUwJLEf!%E zT*4W|_4ciAe<(m`mH_pAiLjH&A?i!n(aSdqvegueAsgIU8_3@JN-DK5ZgExM$0BF2 z-CwQ{sjATj!SBNb2Z@I8GusjKtR3Hal?dg7zz7zhQD#`D|;qVS-TA z95+?>a%!O-fcQ0nXVdneddEa}DP|Pix;#W@85eAt(l|HdZ2H9YbWEirDeRNiJ`+i6L93(swdeDwuAr-9E+I zAxCuB0QE7zWD63@iT&y6QX%~mXytAo#>FGy^yl&`s^aFvPPdlU>G8bC(B#Po28>9+ z)QT7JeISoL@>u*sT;nF`rO{gFT6O2<7V$<@zYr%^ZJxndD=z)yrrpo2t!UB$?B_Qw z_VtPV`zVf4{Gya_B(dL3bL4XStRjDl!pbC>fg%X|q)E8%Hv5?~Fj?To0qhljtI-`9 zC3ToVa6Z@tYPx^2(c>h0emdk5#_e@u)}k)M6O4;*bl2_j#xPcQC__!J^SXhxMBLHzLO2iwqZNzmQPKSbiCQNu<+=X?7x8;$s!H z076ChqYynD8@nH*^;DoR8`2IVrX7Q$MxzZA7jWI^3c@?`H)Q|cnRTD;wL3?&wYV9i zKekA9lg)-{HNAEGOx%ZS_Vi_hv}B{QDgN=(o_=vwRjBxaW+Z7Z)#Yg4GZU-r#ZA|^ zi-G?7Yh7oDbNCu=R{!n7o3}@2Cl9bbnWW#`fbkXW6KhJY)aQvTwRUIpKGF@Qn=_uk)xaH#^z;C?DB@ywE zjFVUdij6(&T2)bP(=X7Ki(eS%#28m^ET=>@Uy8+8I|Hx2Kv@ z9<8V+yNHSrkrv59k?PE(Lk5tj=$_Yqg`x$bHTOwiA94?Pwr33AN=}24q+e4?!>Qfm zPcF72t&g(6nER=hA-RDDRFc+tcwc3CW8au!StB-#DQ9cBjy67oB|ll|X~xMfP71YG zpBcXp#z{wKZ#=O#Jl)=%UG1FAh=m@hYR!vbDTD91uT$%gL>2Q(AS67gQu@r&*LWDo zXO2~R?%~9xj+`=;MQ)q&5XKp*F~tr^JD*}H`l{8{98n`kgE<1>i#8frJ!|I<`ho-DfuX0!!d7=XUrdxpEEP*dhcm3xtWHAP`X_NT9D`8z)X;Ca!+TF5)oIi(nf zcDHy_mE1ElDK5&JD>xR*rr;QLJeK`Wkb_^ll<%_>C1TxfE?s9FukVv-1U; z#YXCK=`~oY;Q3@UGI{&tLBnMbsSAL`a{1o~>BkZ!s-LKbXxD`oA) z9g)&MOTkGkXR9iT3u=lWGwedP={pF4w}5#vT!MDL`#3XVBFLL&V-CmE<0)edF+EN6 z^t}UH0ZiCT(dHOZcR1ETK8>}coh5_(CjKuRRKc0?JeZXAxUREFGn`fMYdb^s8~P*{ z0m4FBS7}j&wuknnXG_+-?X?u!JZxbcqBbOmZ8`a4BN$8QHX#S_h#dDp`e9}4+aIr7 zj$`jc1@WyzMId_w;yR?=XE7AZWWb$XpK|npWv2>?d8lga)igelR`J@RSS3t$MDTjd z>%^^f=^?i{hLsU*U4&tIyUUGJl4_1o5AGE$ zt*4V{r2F6fFDCWJCOV_z{cG(}+Y}y=FTS?v^8FI@*$=qr?9t2Pzl<$t#O^&sm71CX zNAoe`?K~+O-MpohLgAQGPtVfK`#)OU*3Voe{eSJa(b@Yy&R)yh*YjXMW7U7I8)c&= zg+4}k5t*&>naY++gB*b|hinO?7|-6esY)O3i+Y+#{~m{k@8FKBlQ0CdVR?^J$YH0mj! za6A{0qg@f$I+t%az`hF|vLUVg8$3@lR6?eKV&mavZnc%foJ9iaO1V0yXzr97qmiV! z^-?&8W1Q87Nc1w!OyxWzPo@q3OsDeC1)O7-WB=ji?3uEk#p9-LTpePJ39D{B#vJX6 zssYVeF1H!p!|&!9RB9 z|4a}!=g7vLw>gE~A8J}=*v@~BQvVFzAHd{jNQT(Y))|N!>w*R)a)1|%N%nZ}wN<$M zs(fBz?V_EjINL8<&uE&EbQ4cO%!4Z6T+vlmG(Q>TE=AXMBsl;K3~*Qd*-o6DvECOK z>~PahHi_;->EPNxB~^mD-At?YgBK8+|FD-6RxI?d?^kgJ$xZi z)5tS4ow1a0xHYj_koAw1qc0&mtxB$b5SLW;%*3%~_OPHx2Rr0b1)*Nkl9H!5Bs>2J zqWpzg7)&W#DK$nfG9%_~mJvnF8f^wsUb^j&pYe@MH>L=PyhN6ktRCc2y8NL}K?95C ztGQRM_fVt8Uf)1p7+yNqkpH2V7hUOI?kRnxT&=HyA8tL?_M$7UMRz@Qxl zWb?6u5?(a{flTj)wq>03Hw@y06+JDE{H15HaH7iHqA@JxB2wA|&G#{~cj*(+Wo01MlrgkQ%9=5Z_@eaOd6`Ec5jR66 zp)oB(>Vj0)`SZW*No(i#1#|whR<5~xS1lSQM?7j>FLz*~zC5GR zW%Q-!zya>BaEpu?Ezv&b*w|EheQ&7LDexQddC+&b5WN5`=@Bx&dHfvPdia0?>nM1g z_T7zPZvN$CTKJ*T+~?XBKHlL?+itr})T+0mK8m z6@%beg^@~l25@yLaNK$9#nMjcKs%|F2J$1lbbQW2;d8@}NHPyIJEQWSUq9Qv7MQWn zFPlNwfg={Z=}jLv;vOL`<016q(=sz8BSbJV#!QGe!tENrK)Ax}ZAGD!fEVpR#7_PC z5g~-AO~d#H4I4?638e|Dz$4dH zvD?L9M|W_G;EaYJYBQYf4SQG)Sp-`OMWhVHcG?d5)*JoLgMi!1)Q|D;GWF|WuYcPw zB!blwh~Plf&cMR=6HS9n|CgQ~!0<N(gv<+dm6`e{8+zdR=;dc?5cXcD>%MF}=Ut zrsZLDNA>u6J#Q%DYr<>xZ2Jgxy^gh#_V{@{zY4v+*1lie(%(&eNYkA%IRI}@trtQC zVv~VSMYa#J*dadAotch#rv?vu!L}2%w$hW{7E7+~!(wQBV|0Y{X5F1RId>vX7iPVF z1d5Ggo@SK_i^f^GO27|gTj^R*DwRz=-+2Le(6U=nb4^n7vY6sYKAAfmlkJz*k&b00 z8EOV=G@q^(p5#?i2F^je*Vr$2Hd+02xm5#U>vJr~#up}4`)r+(22&DC)rV{%w_-WD z3?sX?oyVzAVu&Vg+Ah#ODCzR)Nve`S_6FyM$|tbh;ObjY9WW+hdz@vDZ!yJm+v3ED zT;P42VP|wp(r}#W)Mxi# znC3J)_T6|%zI9$zFq24dY~+E~I^wKfr>uO&md(Br!OKv~a5C&SiUYG=6)vG%2$eFZ zy7QQp+oqmS7l41LQwDV)*RyLy2@KEIac-UDkS+FA6!||9q5jUjf-L-e)MMzTfAV4W z@6ECn<7GFOCBv5N=4CfSGATSC)k9{TNp0JH0W+qsjTY~ITOOPGHEv8Zrt%)H4V6FS z1*-$=Ca#JQ+ocgA-O!adWI!#EoG zMPt>0qX^?P*Hin+G$~q(n#LZUwZLgoKjk0_!ll-$JgJS6(FGM&zeU%z0j1bCHPb00@r4h6Me0lhepgWoA|YG zc@L3xmHU>n9J1{ia0p?t=^8~qEp#dTT2X)=&jd#3q#2>1QtmM{GGX6sUAeV`Jj!;> z$=vNUlP*c3YFmD6poUGX*&On|vqbReFLnBLXRj{yuQXGdK6!1aNpwk+@|uAYzs&3v zSaH1kZ7euoLX_oDnHcDPQNW;F9M0;36R zPO9~Vjw>S~)5z*pA&P9N(f9&2O4>s0I4~Y}-0x+uE^h+dH=HnH}46&wKAb-|vg4 zsOai*qPi-(GOHq=?0j^DehH?nSOSw;?MILz)-r<=n2}-P9_C}8N5i60YF*n*b4j9h z^mY^vP>H+}I*)+v*&|~A8ld?dHn_b9f5Z7u3CM#sXJM?_o-5q174H{FV->;b=#t57 zkey+Gy4>m}wXH8Lg3LFSAlWbfE+(_?o6x|(?um2aS z8_qAHprzY2S8r}PecA2l;RVc_Dl0qYueMExr2|sX>IN~Uc~QhTWXmvP14{N!RuL*n zWk75S%d|;+^Qtb>qBp9y#$omx;Xk7lR&&n4)vS1us{a(c4F1<6rdv0alNVg%xf7l= z+&ZdI#4&2auM~DE9H6auh-Zb{y0RL6Q}PzrSfp;pb#-ZTipxS=`#V*NX4RPVw^td5 zIxcY4@~zES+r3a1w|Pv*D>x8Vc*iORP-D8yG*oFA>`Ya_ag3pXY6+q78)%G>Kp$>o%Oc6moYVvHB_wB&z}4lTWUWv8j&%~xm5`7b1Jn>-7{8kfC^Gp3LR#Q$#ZADenT7ghQ6erXqeEs_46gmB6{DeohG|v{?a_-Uw}%tm3G~BcEDBUN*GiHuzK_( z+MPdE$i6bf_nlyny2a&l|NFDWsghl|x={sF7;s*0fzcV4bk;9)VS5#y5avO>Zqm4u zae+THU2e}m=&;I@oe$OQsaUv!O5Lg|n6Qg*#;mmJ0CqA6VsTB@FjeNuqWF%*4>&8;FH2bd!3s5@4?YnS51K*OA#9 z96w0qOYrTZ2a!qTy{jrAP{2ae5%(6XME;mxxnW_R@o84FW>mp$g`mg?9qNv^Me2?! zX$JBS%3twN>jj_qHaRxx0d2v-p<|TM2Hk~hj%56nNPeJ9#1zPkj?qEwga=xS%hdHq zrNi4gE$dfBw!r>$iJKEZrWA+CPR~kq*;A)*apQt$Q%HYbW>Uz6eRz{?q*^$xu@&81 zn?);X$_BtKYmLFbCV(B7)j{LQ{Yw9PXq8;_Ia4g zF1}&p(&WYDs_z4t=XoL1Qr`z9N;)W%ui?gu;zSehh-y<%~Jf4tJcdf;N!|#@*U-Rq}I!lTx4J;#iXD2$sa>09E6Q<~BlN-~Dk`pL6VRY;Kqp}CwhUG&oP&*el zxX`AxFQ(Vxk%q61+~gt6^|sfH<-2)-KuxeTA!c2vDN6}3Nl>OaXDUox;OXR$Mp3N^BbFFPd+96{9AQi z-t`*xqjlcjRjnA_k8X7#Z$V`VRIdr7!~XE;j@b}zX;QfA4Hxj|@nm;agh3$>If;$*thi)33nV1 zJN{MG8@CLb`NkR}*PFutezT|4huNHCVM&66AfL-BD5ITNvq|$KPi@)yW33~;TwX!# z586h2pNmT1!OYnjawaCqP^ME%XcLE=_ba6|tg$tN=XkU?b+?ey6nMD#oVq*|y5Ivd zcH#nx?ZPR?5Myw=lrbY{dxq0|;<5^Q$P6p=gHjDw(!9nF6nVfKSZ zeL}V58-Y*$Bn{LC2?dOa_5bFShhSx9;%cR~M3F}1Ep^}7*J`QNH1JR~RbgXQwGa0J zoW8ZZdFTBdO}oZFm+F6Dkkc#E{pZ&~{jWzV05jMB zMkZJPo~R*hh2J`%dHY#Y@C**+Wk?7e|0kgnw&B$7#0-U@>+U+NB5{#e4f!Y|z;>~l zWtcnpg}dl*!=A%6=_alN68?L9jzEgj_gzu% zbmN5K`zeq8cz%6j}Iq0H*Gm#s1m&23$q?Ysb;cdBl@|`Wfk+HHx0C;x& zem)Vf1AO8>IEq7rJt&=h)HFzr>=e|IyI3N``rCS@4zHqM3RzJ_2TXC1om5a! z2oEq;RO!BE#Ji1pdF68jCJ?715z*A(vk<`wcY-WK69AwEnMiEzCrJrS+>yFab5@1X z4ro|VV3gb1q7btkO#08YmT!!7hNOrFC&jjV-n!}M2mLp4Iv_{Hyoi>?C0E(EZk~| zc2XtN$$(ONg`LFP-715A1CHz^YN4h*-zBPiUUcn4Cx&D^q~*{j!Xh`MKxAg5U$6lC9a z@?@$&P@?WA2X$$P-8RMm;@wYr!SZCNz0B_D{siGCL}c7sUBX2v2b=@->zPp5LD|BRgi{2kdy!Hkx>3NGpM)?s0WSfxum_?&0UNgZlN~ z*ldb{gqnj3c{o}7=QWyPfMVvTLFA+UPzJnsuTTpA=+M;nElf=kKC^lA+d1vs$sS~y zo`b#J79v`l|2`jJc~*7Z3z2K-4*)`M{M&3lmyTX)4kqge@pO>#*-7}gbafH=6?M_} zFtHJM*1d5{)BoWHH$!wojyC;#HeB>X!qZL{WwLSJMxk+dKTx?^82^Hp4A35E39h3tP8|+d22F`w7Vb?jt9Q3=Q8B<3_WRQ59m!@l%vYMOLrZSxtG4cD4~ttfKMsK}Ok+ z1;Y~htY?l)YCmJd9nZ_JfVK|7H@bBm! ze>n+BitoIsZUp8;_nB$#bw%T<;8SUo`_!u!jA+#HMa}{qrJ2eW;!+Q}^96yukuzM7 zmnnKs)jq=3L=^iBF6&R<3_LCk`&D5{$JMcx_o@?3vgjsbSF^qk6#Fn~UmX zF>g|=l}X{lU<1@Dm<0MlB3virG*sa!di=g{P$3$YGwAZ>pm*frIkoVZHns65*3;KPAVKc21v<~L7rzQiMRkEUFEdRtAbXHvrQeJ4X2!p6HO}|k|L!-cFlCc=>X1eok}b_(ip+XRn>?%fl?jEX+O|(>%LR|HV#X+dpSoOQ6bV}7PIJvsijKy{+w3O`(0gzfr)bWgaixk%$P(Z zXb+Y^Lwd(m1C~}|>Rt!CskOCi87zC}Hl5EG5(&Dvo?m-_Tl(BM^RwL$Tnm;(Y70=Qt9*%Goz8GghD3kI35 zhXHWvSM|7QR3~Zso`=IqUz{FqUgYAzS9F_N<;%EKbhP(T%JNQ>3Dc}x``I;A=T3p5 z*jKzF(co0T$)o23vtSj4nmT?`nj9Qd5XmB9=!Ok1F7L)-C(r93@CCAiYg9Xt1ym0* zuZ-Z*ufN*!$;RChep#UD4poy(VbQseeARXqvb+f_Z&${TPM#KC>IgVJ&*ukuB1HLN z85euJxa-SaJ7(7Qwq>1lqXO9Q4C{XWII?@g7wraQ#}X2+m@I9ad1F3$aSwQ0Z7b{y zmSHSXjjZE7hQ8s8cNh3s-sNLYeF5@!?BXf?<3Dp0#$sQi!H)ude8Hr$NPy7+ZhAKa z>;-bQH63ms{4(&mq=!l5{#XrB$fHilRA?7j@IU@FF1hZW*b0kfo>9aPd5u>YcH@*) zR-nL7{&~X2!BtiBoFFsgtmJE2NP5m$_g)RpNJR##r%n zXvf@0*Q%4K_O?#n3H^A_OV2lrLt+6yI*}$Jv!29{yb0(0EXF8zfQ8NU#~sk)`gaZJlrs1M`kEN zXpEk`LyqH9d_Yl@j^&M77z%_xr{ZAAIMk9F0uUG-R%K}|&&P3ByDV4ZchiSg4IOP< z4gj)XL>YXA7tDrHL=ml`8O05<^PG&ybRkG447(IqOL`!560t_6LX6?TQaOz);7b8O~s*0|8AGLsC=n5bm(R(!a@ajfc2BlSJh)loS2IX@jD+n-yqVZzbAu zNzJt1LUL{5hJVW%6qQq>>HJ$?u!>b#GORK%Xt+^9OvJS$VSb9IAN4UmS^y(~h@QZ{ zR0_5EOQtw6ld&scaCE3bn=x}-tEIU#r~&cXmE9oPwikM2c!jHpdCg-xvb(<<_66~Z z$dhSI!&obHNXvPvNyj@{tcC-mC9Ab$kk@{6YLwS|H(@ut=Tm9^fccGKg?r?D66{av zbg>)`GX>IYC>CZV*{wBK4(3%FPn|%hIk8urps9K^h9mr{HqPIKbZ;twkOWiwh6`X9 zt?HIwA%6c1#U{@sL4ho7zjE#lSfZDrY_e-hUrx#gfoU4wa$sMWWwMdnlHilrgK~*c1cgk zGZA%5;k>RK?Ur$mP8!aFxdCN`Ed@}ahk-_c-Pl%Ij)T2%DTRZl|5@LGr8u71vKw8;B3@9 zmyhrJ?> z0(U-dKEmPz{CvE=-rRdX&T6lNmUjevzg`#apRWO555L&O4OW&C!N}0mh4LI!N= zD0)08I=js%fPe@(Jp0URUH+c_pmTN|Bm2Yo8U=(Ksnw``DS-)u!>3h-R=Hhu=6Vn;Jh?#I);Nm4D2 zyZP(Qv`%D3S8uxl|HZ5HVQY6$y>0O?))E_C)juWdYlNi=ZIyEt@Z9RTu}U?oHd#g= z4dn)!t%V9XqwFIFZ)96FERF%i8ja$;a*Swg{O2O^2JU`4VvF6a0_dj?+=Fjih}aq} znOXo2i8egB3oz^8q~8ciWSyUHcvj-!>fVbjvCEJa?K7KYD{Gp$=Yt`S z&&;+SP77mW+TGnKMvom+^%DoXdJx0S==lu?DXet)2OzpgWn^#1aQ@fh6MEWCI@}Gm zb>rEF?2oogMDKLVXPT5L<`GultU~Q#*5JF#S^8Bo3=k_r#bux?Qee?%Df74OsVh_?-yl&(#ra)CSDI7ZRFCtG=yH~SP#3jO`su9>#al617+_tl?^uSgg2`-et zFt&YX0Y{Ik%a~I{nig_%(JmBjJK`J}Avj1k?v}|oC^j#HmwtmD3zk?Tt@Y@g2N^hI z{1^3DBY2NKYL}oB0ai;tyRB0>!Jg~Nlsfk9v5Bl%fS=UAfM+GkWK<1LdmN+ZAn@3$ zYvTh&Zwf(mKD=G3KnJ{T4qgny;7O_MMF<1XG}UB zLHi25i|W75%^@+-y>KRZ?SugJWa;7a&N4_9Q^EQ^qof6H+`mQ%c>#xhL@N{8IsNEe z12*G>2;1~_0aJR=)Z_*Aol|<0aH`R)J4_>Vtx)Vb4YC-MCCJI3^jO?qZYdp|q)d!@ zVr}spdCL-T(f-J*N?dFjwL4FPyPq%@MnC8rcl`I9;p1-oTsqT7Md<*{)yCpDFL}>P zzt<&9{@DCppT8PO{Wp&eblnxxNp_-i>aMgzH?Lxm)TlB%wd^W30R65Q@Ni0w!uDwGNmxNV1sFjKVDt}0WVq2FrHBKQa3!frE?(} zr=4AnWux4yQWWo8v9^q9LcXi}L6*}1qOtUO168#Z%d81cF6OGb;E}C-)+%u2J)`4G zXCNWyF;eqb)AZ`sHv%gL9abwJDQk0c)g>i}yHkr@E`Ng3x2u@S<%JmQ+1 z11-H?o;^jnYAwRo@>ZBF2a|Thf5<44t)I*4SJq}0_{el~-(ke`AZjIsS5^aXm7`3V z>V#Up=W#I6aFhQNT`{a|kw2}cyXd24T#mRRTjj#ceTq$sp!tpJ-JDKFU!?uQVV3e$ zpw#pFvX}HZ^%D>*fWJec<%zm4JE63YVFFj5edeUvOzOGw`F49kunq8f{@7aC;r)L3 z($CH5{row8zw~i`^l^WAx&8Uik{g<%_k51{+3S)~xa`292{p3g2j~+z=J~4lZh!e) z+dr4Z*Z{ZQuuidHpu#d*+iS)*%STD=JzUWf`DPDX5PCpHf2IUNt8`^rqCd;iTq z@cvEZJGN$&>vSCKM`sSLm+}Xm_iv1RYHpFO#iWwujZXLvo@f#e`t*!lXN(iqMhJ~j zpsr{DD4vdox7BO=fSZ^)@5$k?T^9S%I*(RVj32=bc-LyJt;2XKP;*#N?L6vR#X=x_ zt*VCcHo>{{(C9?HlB zTCnu~jt(}@^>^<3!{6BHeg8le01kDxjG2SXmOD1ZtXdS|Da5g~ zqr6*@sN*AH-3)s8;UtbO)VpC$8P?xfkt9AEIG@D1Wg&R;+(_UgRQ=2nB5n2p)O&ZX zhMVxM!Vyd9ogy)?6JM8UnuNyBio2ZKddkdu{Q`bz@9|K#-OJwv`-Z!S3uB8URPfR0geD6^naRM{uI&~ZnADHj`eizEvCvgBuV<@cfExlWiXOW$44Gs>u@Co$ea46Zfr}AJ}H$pvVR8wygmnfcD{_iY%Ynnuh zSRk{&oKTO$evRm0Q3a`9Y(ng^L zs>E~9X((4c12BmsaM8NKZC2nh1faUk{u>IOs+{x$m9b-qH*s0n;P+HO6CrrdX5IU_ z!M7m7RNF4DJc$^q$^us=0YU28zDdB{Fk_Tu4sj@43nfYM)HCQKF37)X4pD^C*^37H zYLJT*uXoW(Y)f!~1$q|TQ#OwU_A1zlezE=n``#P==Ue~aNNgO1_UMB&KB#n)rs);s zB1+?IGbeMTl;~|JYdH4{{JX2l=KrEZU|fv<s6a^$y@{a-TYR_q1)z4xb$e;ji zqj*vidhXLO>N{M5nbQa3uJ{_8knKhb^@xqv>D*#Y&o@i5Eq~sdACHR}&`}K^uNd&#n&b z&08;OSz@rpn=VGek5&D1Q4p-(Co%;D&`!^CKM^iiq9{qOuW6nf(u<*%a>tM>BU56wY-`73!cQf*++s-TM5G1Cel0pbql%5~VqEzpPt)m_@HHbz|-W&u7O&S#M z>!uC@$BnLsb`STG27@uAX*nLq_gMH*=m;C2Z8o<~p@;+K|y zi5ABK3-r(rjVS*ss0smbHHUsL5@~^GKsrc1%=Aq3#qAZTHi<2XGZ#iovsjQKYriNA zzphLhS;$IM7p(=%oX!FeE%g-@d|(?Q67v_U#Ih7;!9}6yJ~QMtRuu;gL((oR5W0x1iLijIPU%D+VdZPa&it};d4(+|~`tupw_I{u5D5Z$c~ zw7!n1naPM~t-tV8DH@7V^bXvLNsz`FUCf_rmc^;_5mP<8mOUKs?!piil89xmB>Ed2 zQd$XHk--?NK=2MBQdm}dGNC}E<0>H#ZFCZz5h9p&-Ui_e1<4^_Mz~^y51He26`m!_ z=OR>7F?1#~tgn^xMw8+2Cf#II+w*#S zUU`k+9=32`uiwS3C*bY8&X?WuvHz(59`sXUWH#%b~ zQ@&L8dA+prttV(+?Gjo8`%sV|xRxl00MR5zSpAl0m_W>q!m95mnm9|E0Y6w1KPDIt zenR?VsiLB#Oz&w|*#Y^w_m@2ZAgYt$jO(Ij1PlT4qDLFN0C}|H}Uc4EM^=P(8e#c#Y?B-QO0Cw9? zNCO$Zq2G_?2Fm>$KTV}~(WI;`G#pL&nkwJP|4zQ4Wmt~M{)>cQUXY{vr)=gWW`@nY z;EGGgvK1XfZ)*02hI}fkp&g3idL#3=RbUMc zild^10E)9gnxWNOzygkerI8?LUP5K2az0sgItjdt1$Qyaa%3!?hO3PiG;cv}44AgL z6oVRBFAa@kfyb0aLNM+pSJU(wI9K+6l2V`!!2Tncl{Adr=4M($*wm6qjh-~XzR*L5 zXQTmWvd-77xa$dVE6;ebE!XWV(1pEJPI;mGAvpyfP1kJw2?O0Ev-D#`xKq^|iI~*S zD9?u+MA{mrm@Q<)_XmD>lY<2q;%z={P*wIbRg1cLfLB^er60T7@Mo{s;Mt^{OTGMJ9Gvd?_5Jvprt*-c~x z^xdP{I>p!u#{&OUm3{)2kXeuPJ48lEN$NyE8(M}TZhmB7(4;U6OigkI3k`0(R$0>p zVWZ;ftPn%L0Fxs4a~Vjk6F?GHS~3%AibbVeyT)c5(Y)rDY&Zr-E5mE-XH}4EK^g9C`6b*vLE>QgVL@fy7MD2*DP5zZ)gkr<& z#ZQ(=uUxgRd^VfJRMf+h6Dquh9ICJ-!RX@Nr(h!mHAvkfU4waOS=lxVl`KpzKOEO? z8dS@2r+^U>oQ_3M{hp*Erh)mh3mwK1k7438y2*I(G$&X+ zGJP4U4c`nU-RYSDQo-ye52BV4YE!O=R$UfZP;oK&$XYgDK|UNkSmhv!tdbxIOkA}z z8(v%rL8%lMz(F6vMdqGWG|R1qyxg>(s6GNQH9eEUO6yzgOqNm}sh*=XUza@j-yO1| zN~^LE*`zF3yKZ6l1OU<$7b3IOTb)O;)QpBn07XU)HO9VF2}O>E2wF9XAzHT%+F98d zRk0to0`7J>MKv8+jWjU}d4$ct5}mh31NVTU?v<|wrJS2t0!aP$ja7qpef6mYYV`wA*U}NFeO17J#kfix|o(r`i^$n!V z1asB1<$WKuC3SyI7xdJ2Bdu|VrAT647YlSFn%1AfOtDrHe=mIXVByvR?KVTM5~!S< zFx2|rTnRZi?YNr^fh1_BudIECzdiN6>`1NW1r**p_gl$dbpvKubSB$xAx~D-YHT+? z&2yAI0baSI+^l)HHq?#!;Dzdpo% z=(Z>s^4gf$7R%cX2z;{-`nCJh!9xrs_c17$w+85&_I+!D`9UITfc1*&o>839y6zG0 zAcz`MEYZ`k&%MoDpj79Kh;>i)d+W;40oU9WNe*(SHtT-y zndC8R@N??D_SSi(R^RX4%Pxo&1=n{Zhb=7&=j!w5^Q%}Ba9`KindG-~ePz#oW%opK z4}k09BGLIrESj9uTNoTom_0#RqL#hFfWH8!IX36HcQ*-&{F%q0B$Xk4#A&acwAS=;H4lRYy)FF><|o}23G#PJXJME6m856|WpHzI)U zb9n0V_8&&=j*0A=?(iV=i%zK$M;rHJGK{;Y3rmj@``PLguyjj>9Kpc>qSL||ogl4mkM| z4f*eTamJv!73{FH<#S3Mu!dVwj9ODp9@cuek6(HM zeREE3EFs_Nkj|)%A6W-VQ7@c-4ix%L0L&uW%h`f!=~hnw*aKnk7tR>FSwEikh;uV9 zicw0_VzTwvo!;#%IWl?oh2#<+o)LsSx22)W=HqHNKM1neBf%y>hWzVpFjO4EA9GYg?E6x*ziHv(> zw^Z9Oj2A%yaEoV4HD+Uq{Vp6inp--ekU9!hJP=Ch zy3Te^ois+bRVkZ<-BJ;wRi5;9o+4r=wPPbKjZ(e}n4L%tI4`O}(o5li-&4bp&tmSV zDQOV*a4kWLxjFaQ49{3j1 z%m9-gfDyB|fdH0D@VEDaC0ZE4 zrD1vO4*L9r`y15wGJozoej+s;a#Nc99rU_04p&s-XxTVC#AUn96LQl@gAxkF(FY_w zX%=L5Di6%y}4T0#LF z3OFO@{|3`=u%{9IL?r-lG5&9=@!wN5tQ<&NCsbZinz9#fw{gMoV8QU=kTitj!~syc zRG>w{*RSOVb~CGX3f`e&JsuZ#R2oa=4SYF3M$0MN!-fr`p>NN7YKv8hCySz(zg}JU zS*w8MSvezN5?k!Q*f}kPSn%5gf+G{DKB6RcfWLx&lQx`OEYe{pFbmt(R1H+tc|8p> zuDwB=u@z)3Nyg8pW^N{i_KO;di+zw z9w6T(ixH^XX}L1V&I5ib(s37jqU=&5U<`#flP1Z4QvMZZ$?JMe!Q+2R=AZ{@`Q>by z0AQg!CQh?e4FrvM#<6y?xHStxq7#V_nq_WRJ)9iPMj~NZB}PWpc1%L%u(lJf%bt`Q zU`(0pB$l=~l50vIw@b?FGj0KrdOEC1COf+=L@Gp^_&4gqKKfq>E zewg^UiAAbnnv^L|OUKCMZW5{xbF`YYAWK9uzda-(y0iY>cvy zvM#lSoMz0zS~#yw0SOifRY;6h^j-<1TlA+ELIA{YB#5NAoZ3OEMi`r>S{?a$9UF9U zhlR+nMra|gPJzyN&FwrOv$#y#g-$)V!V0&!BsDR6b_kmnx45rf`X{59mIkIthH@lR z9~gQu)`GO<{Y_D5a5WgZHAF_>ATh}R4sNRPS`;~Ak{WniqUlt_U>`zBzAViyCTw1* znT#Gud~ygUtMQ!if->zc?fVs&C@nMEjShQcgKs|%{v4bs1u`MvhrSM&6C^dgRI!#$ zVqRdROngW{lnml)pMwCnA z`X9Fkqj9-_w>OOM*QeZ_9Wa0&>*2jU|NFke&ew}Ci|F+7S&P7q7}Q3ttIz#fE#Pwv zOO{5HGl1&2p7$#NTVD31R2HyZ0&jmCUq3%@P0LvMN1-6YnckATH%%z9!}cm-Y=t{6N|^ysv3={M4DJt{t}Sef$ApBwYqRJufKh zmspriURWf)!YQb;2srwjqp3T0pZr`4Qr3E=Ez%%5;MyPH|IF~h%C{fXpi>Y~cs(a6 zF{sZdwU0Fs;v>qwCE_!>f1>$UjaC5LR)oFPFh4?b6cQFijgdbffTBmLCwTWg>QF|x zUw%kx7n<#FS|3V>gH{EvROu@DO>pL+%r11Z8%Z0go}&?tE&;-KQYCQIu;?Y)6nB=z zgm`SUL*{^OnsnT6Dh%}ryQ;0MM-c)Oc`*K-xV7tTw zBJFo05|d+X8zbxOZl*15vl0m@Mn;x(%Mpqzb2_nH_qai%&SIsG#*^cKFk>;52EmI@ ze^Oi=%%daNJ7p41nr||G_s!a9Dfra_)+Nd@J3xmj7pGAG&B3!jjOPUjv0P2Ts(2oo zxO;V9V2kdXmFaWA&eBGs)xDVNP+{l$^CS-N{V$Ef{_nhx1xravy~u&yQ`YqSzfnC> z)w~qt^QJo;nmd}YG7u?rmtsL~NW&TsE{y#$kO++V)u;BP1t2H>zxzxDiM3 ze!w$H8srl+vDy`s+SMk~=Qu}wkeu@l;a`$CJ;4$QEvm8VK#FAH{ZYo%-Yc$L-k0Q2?EU`IE&y@W^^2&p&ka>vtVHzP`1Rs##PJIR|4~9M zu$r?~T`XzcZ>Z!O_+!;Q#@6XfCOA!F-4^sGX(c#(Q?pZU)k~u}tHWV|caz*i(fE?x z@SiSH3yVR|SHs8LrRgJ0t-E}jih~T~9f=A!w?tUKq=G#pDgS`%)hhg0$T}w&KtX|? z0U>)a@VFe$;mI+<@x>Y2@6x|PLY?sqors8a) z5J@pWl=`916i)gePUsf%)4UBGy2kKWo76m?3w@@tYGhn!AgzJ1CaTGqIxktQKc^w(7}$O=Buz0{tZW1cp**H9{C;pq z?Z%~U>2HmCz;00>r{PU2_gYf;8;A5hAJ^HC=GtxHYH(r_QlSRd;`>e;O=_~5&N@GCpfdd_XWtN_W zjCuN1JE#)y? z^vXhDs5{HJNI?GvR_B}s)}OCGf`_5>j{}nb6~15%jLzjqPPwA+VtB3~4sJc&Go#LJ zPO@J)s~%nRdl@kr8Xc?16!H;46dd@qE*@^iy*Dm45h$fRM0)>L5vqn*aNp-N9P(B; z24*+W+x3uv1+b=KOFJ%O)4IuNO1b^^Uq9V(pZitIMx|8tAaa9R@G~~Du+k-|9G2ra zUDHhPu_KK*3j=fIM{=1tc{kdltntV*4zfX|H>jhP8|oOLi@0~~Jt>a9G{zNk$HUO1 z&F0Ol|Mi?6)`-3Ro{nMOyjbG_>2`>!4jI`)|x-IIj9{Z%;apE0g8-ORqV$PVQfp2a7B#J^{_ zN%vW?kTKgo6ASRj2#LJg$2XLJD>roPthICSL2gUN4t#%IM|O409F2nB9NRCv1xbis zT?3rOk~JB8ZG{o^cPxJBiH5WN+TBfX>!H4nVC%&Vuec4j>n3pm7T>$QfA86QE@m$o zc&Znt3@)9tw~sNul==9lZll2P(E3zXTDc`1O9hW%;!6!(xO>o#{|{O392{8iEdIu} zwXw0WZQHhO=Y$(O*;pGp*^O;$H@0m%dAaX?-}~MBUY%22U0tV6&D8ucJw4r@PSAwo zX`b%F*$9g$B*G+y+!=KycH3*-`FHVfzcBLk)(?FGFF&C3=lTR9p} zG<;7HuZsA5&wn=5JpA37h_c-bmE_{mSbUD>9V}bNDI++9@|}Nz2lbX-l*%O6y`Vm_ zRW6zyNK1dbwEOy~)}O*YYSjKlUj~)r_|kYm`shxSsxa9geyZRa#=A4yxh9O4NYdN+ z?WJiZWZ^QSCE&g17>O2sQwqL9brXgjDFh!Q#~GAR=ZXswxn~r-#XSG%l%=3L-aQi( zg*m%R&IuN<)00<0aiaD?0hdQuI!*Vmqbq_EUAv z=YF*66zP1fHNop%jydOOyH-Xi7cK%(^4>?G($TN;I2BESi@I4>)vG=PM-)$so?(P@ znk@6f#)-Vt3_n-6feYUjff#R)X~WO6f1z!BQmRfEMIW4LUu!oD)}piQ7mx;UK~Y?16ECOWN*OzaG}cQ zi|~nV+(6y$Kly*PL8lS<2c%6hS3e(Nb26%kCTpG3;D}PmjZ9B8pZJ{IuS#Pv`{Usys5)>f%&eihs?c7t) z&k(8j(V}vP*$$@N&{k6!yS?TYRVU7z!ny)h^`dsQV%kX~$BmC_wvbE}Ro)rhIqlww zXQ^?QOczlJZPHx#>ddx*9_5qTrIW&GrSChEL>ARlZGS+{^A02MzDjv*LDhG3Z=w6p z{qfq5yg4xx^2cp_dKh4Mb@14!+)Flq@w)%S!=JYKN$WF!%l_W;A@7j2Sl3TrUBthT_sa zno)^)A6ue9>XsNN_=k2Wx~Jb%b%n;`odpJ+3d4h_pos%pnTgki=i|7BYONr=CKO9S zTINm!V^_`3C0v*cuv(PyvFraXME&?~h+6&U}U z1FS0B>_TBJw1%7;wCqY&hfu}Ygup_B?}6yUd9q?@^d!8JkPwFvZvH+g;F&T)&EWL` zaL#(-2ujBK&+9CfX!frr`okW2x4=$2uATlCn(r&{W-7H0{3R%f7)SLch$A~P2xQMr z!k6;Tt}le2QcGXybQz9EQmq4zL-4G})Y5heL)k;J1-FzfH?wl8OHTyLAZlATRq6C63xMj)KT>` z3gS85cy={%Xo#01X4Xy^zFcco5|R%xt|vw#_`xbT323PVDo3>LS59s&GykUgj{1l= zxV*TmFtdgC%D^MTryHU!4)c)~qzewi4#%#t3+Q@j#g1$k*cMw6%-+JADJ78pr(7yzIAT7_8S@HAnLqdmE@F8mkVnIe!(~S0 z6upL`B(Me)WgHC4QBq(Zg~VBa0z`94dc!KA>OP?!$npsrqMS(;O*2C?ueiCWn1n4w zRE>7h*4FRlzoOv6-|Pe}VJHZ?#I+s^m`Ig%y&bNDHl7%jaFw0la%z&| z`4cl0RU`vK+G9fG*{ns-bW$7(@Hv0KwxQEb1g=4DqI)o_In>6$w3Ubyq_w${JHD31 zBW{TNjU{uXqTU_GI~FB?u^1F1fx`p0lVEb$Nz5AR?!}lpIV2)$nMS^qbi&<<_yZiZ z)kF?IYJRMYvS=crA>TwDS2fcj)qhU;NCTCU%~?S1Bw?){&mW>$PD@@01jMh{YB!MLR0h3*JHgTRS6KMw_o*XCW{SqG8OUrLP0@%NS$LkbeeZV0n! zP3Sol?&JCySH%xF5To;7AW}c0vO%k`KYoC)Z(be#bm6-N$S9>bAEX;o3{l-6UQhBA zQSUqu6KtieiX43|qy3`i?lKH{1MHt{8`Lf+S>C{76%gg5GqAhwTQ#9R zMQ^mA6U%uzO13wekP-U4bH9r#rr&HR?*B->t$??Hm2w^1k2>YDdTI92szzQ_I5P;# zTJVB0sAS^+YBi`;RY7UtVqVy=r$1x?C>?l)oFt-%)biBWlg7}@h z?_EZ}WxMzuG}q0Uih9bw+ugX7%;@yl4Rt713*6Gl)ODZ5mydqn3R;YZx}aJ{&dc4s z3`|2Dq`mvSyvqUhu3`6Yrtcc60n3}?1^{Dy>SifG;x*3b``Q*TQ+qA?c{P`*+p$pm zL7s~EO|eal6GfeQUje4BNpW)w3BsC6sEh$7Ejjz(7_-31S&ne}++A2{wN%orRh-z5 z%0D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 y-LhjGqC_M|pY?DwM`@E@a{?=|@KZ8Oh~d_HVd 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%0D3E)#4AoPON>V)8qbX`E+pR4d^bJmA-aB54!b? z^1k0In5S;DYR+A&bVZE?|Qh!FTK-5@w##8frT$7m1@A8etYi3}nSfB} zX(MIlR$&`p@2B{1e<-hu*#0)1#-#DZXF>y+;X`j`?6VG%N&~O*&42#9gv|G z#GwdC?~MealD)a`DKe(`wO>e%|AANF#-&<`nE;ONX&_EKR~C_?+=;bvB+RP5yu zx#sz1AX7Kr5LoOGDjaIP zS^PUG>+P^@pwnn^7zKK_`qGTSPy>8SWKwGI>SlmGD$Grps2{;Y2F{ zQlE2->k_6OiRk2Y5rWw1Hrv;yZp?;+nI^zB6QBkSt$A17XCb*0;lqo!ZNAYPNW>%= zh3*GS^f$X{Mp^Cx{2XcD0GcPCKBRNBI2=_}=|WkuM3_Fq9RkUlIEVMEbc^d zqfS%XCbWBzg{xN=I_HaoC?#ysiT)SBpvS7tQ?oiPge8*PFl_|O`l{vCk;X9uct_*y zfs~PQo(K_%EStgL0C~s0jS2A_Z$e2DgGfzVHfAX|$@}5mZh7qHHF&g^^h(fk)98D| z(n1JL#K~*HW?U2eRVHTW^&*Po2wNIr#x1?m-z?CF@tA+b6O@&GKHr`ER9m_TiyrdF2x1E~m%--Y1e?uw(h>2Svy1!Z6F z-7Yv^Jz{65w;la6shwZ5w&m9e_7=53?uWMgT<&)CM!Ma+uM=|7n&PwUbMJ>os$@dt z2i|u%#^Vx4+7sA~7f_w~n16`nLYO?FKyMz9QS-oT>XWz6)+Sx#%>vP86-j7Bw+SZo| zPjR1|`xF=27vN7W?lu-GPYUw;91ipgPNln%2zuiEejghG6vGZ@>^d&^JkxeaB(|Ua zK1*~axat7+LOr0Onzv+u+CpOl*ZYGpOshW|dLk=8Sfq(I=sQWL-z%>?+4|GhE-GFti0vta-|FAaB8F;z)aNxF}OlyRNA5{oIvOuP%30|T}5Q0U>PE| z6ON#jbX=ef?C;L9nPM@%5%L7gGNZ3j^~!rAMEz*`V1v|gI=jnTIHwzQRMvdqL}GmV zF1eoTfy~>GCNoYX!Ehmx{SXq5fxo+n??-mIFi$x zfNpEFhRZc9<6NV4;p>LC{zJ|iW&|8?T5;RD?sKO8g?ffE>kybn|Fp3?S}9qpcI!iP z^)M8;^IRHvfr7W4M{BuhU~%<3c*Gcc4HL9w>7b2$<-Ac@BN*G$_McGv;is8#zaIbM zFuG<4{Cp_TFw5?Xwgy{zC^_cMtYKW7gZplKvgO zWgF=ttFabCrI<%FLa%%sYcrrZ4cF648<+;Q4zq+};+=XNS23Yo@h@5;HEX=&*KTgN6qp4W=SiO72v zA<@Z=n~~$hdySv(FG}-|fCT%^Hyzyrl?)2cOaY;;SKrrYN5R8+nTzh}4VoFw^`$+P zN$9nw{pqEaw+{*6?7nu>(b;opa+!Ph<%@OdQ1<~RDEgzp?&wIMKA(16bLyYerd|zg z&p4l_?dSG7kYO(eXn*p5{0$eTKBQa$K%FfdrvC@mWn*Ih53c+FMH&AmqcmGvHkGat zKFbHa4GiuGdi84bfp?>iH^=Mq=az>0oHT~lxin|&478sJNynYWPv2yX6XJWSu9kuG zvZ2-4-Lkf2Wt&3@!6?i#sXpdDg}p+JtjQC}rY(f#FQTxB&UQay{e(E)91Z6COo3LT zFBQNg3$0)1TuPw9;yG>{HP8=fW-N&?y<11X5DUx8XVun3$|8R#5)H6=&|3JT)DRI>S_@$V+N@47; z`=9zpPo#QzzDVo4(KNz2O3!H0$o;7F7>l48bDh`z+xDG|eu8Me~ z8JyJJ3pBxnpD7=R$lug)Z~(e9p$aQp<^48`3EHBZW@PoEj0c_{!so+$LbJzG%4%cA zZAehYTtcBukW9ZPScLXL;{I$oRVDim$HtWXkM zxqxjoA=v7sAgu^6P{qc>uS8aV>U9f14#9yifn3U0!zR^obK8W&(qh|Y!8NJ+O8mR(!HGihCE-B3`Pz_ZOPW6L21s48J#24Cj-(w}&Wx2tQ z+zZP5>}%cMbp+mD1%Z!9@b5UFyx%kKEhXnGQ{_`^f|cB~s^=jR z)bW32nQkYsV=M7 zZ(f6I$~;Ir#{%p^{Lf>X=QM^lMZo?DRfdkB?9&P1-9phO?Kf1plTxqqafDHYYiB}= zN_r70y0$rUiGWZ-Q?>DT-X8F7n`ebI);7|boX17 z)V)f#IZXw|&vic@Kn&ywpT0OfmwnSW-CbE5vA<@*ndeFjJPN<(?N4-fxgKmhRK%zx z`X<)O4|fHH@3C!`EoG_3U4mUjNZ;fyL3+_8E3{kxWCU+2(d)k5Rpw3+FpQ;T>BxFp zI;bu#1#X;`DqZ#;+uE;%y2__ebygNS4Igh=xSjS(-$^!~nWrh@F0*$j*`^ksB|d(= z;B&1}SpLJ5OzLK%koWlgC-~_HR%hC9rM!v&`og=N+2V&LWYAO2jk)2Q#cNvdvw!37 z8R*w>|JJu2guIaH>t=b!xU}f&=3`tQ7lvcX0pN-GQiT{B>IvV!n}%!&%`)`q#$$#$ z^cQ18?h3be)%vWr;(epQTLnIoH$^8@>*OWM8ij=tb zzy3g@zapYvKc?-+YT;vLz(As3fvMkSwuW!pTCL!5TrsFI&yOVj%hNv%pB(;O6pC72 z2!Nx(!%`;bS&D(&N91RIx2AvZV@A$LfFkExRCtSTDecN1?RQpz?kC&am0kD`{hgL? zc|rcL@t4Oc6wvE4vpwt=U{C(OyXIsa&k~1A&v<>00W&Ihmu?7D&D)s0)BgfAy%&#< z8xo%z%RSPFEt`_z;WyFqNkMR0Bs8LOr*}`zNw5F~m2MJ>9fk&%f(yOngr~ z&oB3V&92M+$zR`UPM>-7q>X-@d9L5SyN%9Y{XvXO+ZzYDjQcRaa1wUOHVm_H@(C3D z^?96=GPRub@H+2SccmF}2v>d9(>^)dfqIEJ5dP}_vI9Ifzae%_5_%^oG8XE7bOQi) z^)ab9<>PbrebwEyGFkV0bz}K`t()+EF5+WbQ`i0H=tX;@g;6YcqY$HpT;RbG#8 zsjR5u@|9+WL70B+D1ke}Pux9vdX@L7+8_Vg0BD95K6#GonfBf7<5_oXINA4WWyc|g zr%$%{&;Dhct1EpD0)fma!b!*HbIjv`-j~@a%U41H-?f<3qMq6No`??nsQ-p4U~Eij zNFS&aUve5O?EfRD!O6w)zsxO*_4JcgIa0s0k9t=rYMSJH!OP44x!_SI{W7C5_wn#p z8UwE`=}@nXBS>^vdp&*BLsH|UijU`G#Ly+XXnWkr31%EGP>E8PQA~8#8 zxxsYNYNp5KtGvyqIvZ-bHeZ-xDKgWL#hMSW@BZED*M5;;8pHPshLx{2lH^g?lq$J` z?q-%NpIKuuhhJBbOLC{BDNz^Sv?{6Y4Q-rIgTaKsWZ|<*XTGa*nb5dna;my=>%(pf zf*q?$bIVUh-6LLeTT$uE;SL9(X}k#{7!D?Yzlt^5pl%nMk3vm{c7r7Hg{Dm*22hBKjWTGWB?c?k6cy2K z(TGlKYbg_?szbuXA`GB+&tK_M{j`vn(@EO$YRDUj&b5B^0$L{68a64sF=Eq+at zo>&4i?L|Rf+VM@I9SQkF(Hp-8LRCEi9tsKcnUWnG;TcX0UZF0KDUdkO6 zy^Cf(crt7P?NlIb%TA(#xWRe!@^*OqJUF7fNqne83O9` zhu<$8=eL?@t4LSn%zPfMO}0yv4Vd^Si<~y^52Fls20qJ-N;0pwAXmP-Vo*}CQLRP3W!ycQK0-t_m7PCzC-~{VtZSxk&uWeB@s7zVRn7SQ|?mi)+C(7q&dF;c(yAspTqBy zs3Wu_-4sxTWpMgn@H*HG!m`BWVQ%6pGJeFPT$n4N7QfVj!Y$z=8KdyiiYW8#g2l$; zQRF+rXXe1ge5v`c!3*2)(q&1A*qse;^B8IPK-i>rqGa==4MQE_$9{gB27^~4WlMCv zN}J)Ljw3tH|Fu@O8q2Cc)OhvGjRp4hs=(y71ulEu0`V_ z5<)dBcR2NUa%)7>ViNIM8X52x@HC1iF&0IzN@9j1H4|L-U-QJWQDLULBBVIDc1JRI z$eur~B--5BOuosa!LroO($5fcy=(-vp!<9gUp`)eJUv=7(3>U^qSsxI*H_I;`en`y z%te~A;rZ8LzGhFrhTV#npJO8&D&{doU%0vGmi7n1W$;g5lU}NNmR&!U$VUEl7xl>J zSE_>)pH9c=3D^dnzNCqwN%}O}*Kdr3mp&~pqE9f9eC&@&2Vu9RkGXldzib6I^=ACI zH`iLf#+=`iar9N9OCen*mC3mTuVo~84t893Z`00=k?{Z~qALWZH!#Uqm$m}-xlf)O z1_gt3xjl#!Rml^J3LAo~k-0^_A0>ZjZ4WPDk8{y3p8c%)WwL_82jw~pqEparADQ=R zV*JQb^(i(`-|()h4CwS8i!NOc@M|$9VU_Eq)7Cj5O=bhSLoRcW7%ex`cGCQ3yFGo| z_y;|ZH!Fet2k*95CUb2LqSzlML^BEny>I10%&+|X0m3md(|JLPXH1ITR5T5K{U3tw z$Ca3yocf!yRG2S2m4ct#zON4Rj+wQF_z!*!mmQAX-q(rUey=+t10CQizU%C#{;tb{ zx{;1$W6_4E7>EeLhdAey=(n0vqX1Xf$d|Ur#7h(vB#>bP(?%!s|^BmUg4;RUzi&1S)3Uaj5n`FTCV%^id zMY|!hy;l3vsVBKu?(x?`=b!Y=yY-ecUa_@>SMgnw;U}yp`T95Rzk&$o@Lw?4nxEB* zIGuK{%s+aTGk?#{O%9M(+29YJHICN63AnfVTHjm>P<&|@vI8%I({2U7Y% z%JOnMV9|4&8S&!$RA~F*W%qDeg4as3Rf#4z&|`S=e()i6BE;j*T*m-%ing2s-UD>4 z`VPiR1-68DUoe8OL(a(ka2$ycN*TD&DL7Jt7Ox0qu26=a!Y|Er&_G+Xs=#U`bqq$zgh5z$!ZLnUT!iLW71{)SDEls-__L`u@%gLMxBLJ z{qh3m0hk#jN9&0pnZHQN5PmT4I0h~~d z0jb{)^qpB)Gt9=^rYKdNm0`t`emby_G6)hfeK+?(LVSDOt+CELJhk^b8wWB3a^1#~ zPb$Xc*E|UwP7Wt6W471HNx1}b{m0xWs8tIp;buQKW|1V+Mx@3C2p&&ASghLMvZJqF z!@_KO>HGNl(%qUA+j*c^J(~26PLkVX%*-E_J$BW4ihEYth~H=X`a|4S4zcyUS6k$} zOtBnJT?AlIl~ObsRlL0GF92g;S@0~i-VM|?QH4JkvfE9cI65>?yBMX+1?5NY5u&Zp zX{iG`mJh00)Z#gP$=b7iP*Kh~wyAo%oQea3*h+XWX~#nt2)Qzz2=cc3^#V%>l*^I!&j2&V z?NKm=3Jsn@4M*Wfe_)<)z+)Oot0kliCZgQRIw`J7SMct>SsQE)GH>kwFOs1oufaHm zoqGP)!m$WF5C7jH1FA7Knx;ufpC0}Vf3AgdF#JU?;S!iKR+uM`b(51udmSAV!qgY} zY|SG1`3!`ACF2*omt%M(%zjANL2s$&i0>~o$5OB-AkBNBbij`Uh1H3PmnwUKIg_gy z2H|pZsw9RvISYro(|SR1pB`%_YLoZv=3~sZ+9_C1uq>F^1Xj3weguyvtQpVy7X;IF zlnUy+k|}~M${e}N(GVvgs*hGoEE%0}It^N0gip%Hno#)xdLg{)< z1U>9>xt>-EK7zyW&`2n^`q!oqQkx%4*qnLsvmY)*@9_&jpeq=Q`C=*4kyKiE$_$g> z{NX0An~WI#V+t$_m({z4SrB*xjyxv?B>7m9$tahGTDZK{Prd*HP<$9X>ymUr9INu5 zbr2OWKwif3$M3c-pP%0sk@Kr@Pj+?XI6eLyNp$6$1;kpz#5CD_hag@EsA>)&BW&PXZnFEANM3v@dKw4@&s@I+&N3J1$^T_%Kj+K6ZLg>7? z*?#RL_}6EAX}j0y`#4&jO12v-Y+D*9r=!v>CxnkzdUHTF!-bwgjzN$4WH(T1_C|R!E)H&TS0$f zf#usp-a&0PCLDHHK;Oc@S3~<_yvC zA+UbmKTyN|1w9jA_K9pCB z+hr|`?o{LGx=V4Wx(REm08y@&Rd#58kk!3@!_%%cPg^|M0uc5WvvX$N5IXhO3S7;8*7 zQ>VEDsbSK2h9+!M;C82rEg3-Q_m_(D7!YOL-H(;wgxo17De3Y}2O9-*^`cQ;+aoS-?A}v? zL_pJ;Q0>~`xC{sdUm|n`=@zQK!&ADgayeEy);tRr)NNB~E8Q9LBS;&62To$u^3uiy zQdpGtXUorOHz*;!jXC1GJ!&AQ3so}=I}Bm2M7oL{k8BcBI8af5dk>NsSW~H&@fr-4 zIDwzqOAfRGy3ZohrH-Sv`8NnLD6aPpBz#oGMCjc4w*)c4|L#{~obrWU?(Dtj-%no6 z%kJ2{vi;=!#q!zZ4FQXF=a$OV(4?BB`B*c0%Hzy`1%!eAMXWXL`XWfI-%Ryfu(!{V zVB`fX`V_c*v(+xGe5wq}8Q!e36#$w%SH|nlR(>~qj7U*du?>S2`B@mICU73Qx9;OGJhI8QzP&sL=gBu!w9j4V zkV(0C`^58j)v)n6aBA6;s~cIXsb^vS!+pnWf1w2T9ZgYJlIFwRb-oLcd^k+gp2Z+$ zqCiPutG-&EY+nr;A>)b2+%G1d=3yYYr?vt;-YAlZ(K#caTt09WDfveVH} z7%375o?P8FStvXus~wk=@qjWIF2~b9qGdZQ>*c!FiLiA;4!iE(#=Ob!9f@|!3`L*B zl>XySGtiE#nrgIo~{~6X05L z^Swd{CHBVOdF<&53z1YosjX$37pkQk3|guBZpK#?k_Q z^z(8nMR6aR71i0C|8^Df3g>}ro)SRY7#mxQu+4awq6vKVDY&Ti0?k@gi0I{6FQ>gRv^&@=g);ZhLT^gJs zoUDJWv9>;|38W{V36M-nfSUJ*|aghlDcCP=0dS&Z$ z)akqWe*|4u`SFE)4@BOR>(4d~4TAW&=+LYT&jFIXCRF)XKt{yom;84t6lu}O zIt`jSk--2wdlIq@@*hYwNX-{?mi8R!bs)!v4e)$ys&Vo z+j<8YOomOa8JezjK3bif8Cjkqb99P{-D}yeUifDEnOd@>pTzoOa~Yfp{Q=j4J$ZIRsEXr%dIF6AJlb9DwhLkhs%{vOC z=$Wp_()cAqjr|jo`|fFhd_-}@m^>52AY={n2MYg&>QY5dR(IAZGLDv%rK#ckF`ca>KpzT3z`(QCEp z!1t5F(Uxgq{Asc)v?()dG-{Mt2a9iF!DT+uJ-6gIu6S?>Y1isdM>1{F@j}(A#)aoHr>JU>S*qVPWDW*D-o_$ z?_^GyF^7&Fda-#=mMN}RnsDhj^e z9p5q<_I$p)I)1*r;KvOS055MXpYM;SUk7@-n}&khpN@t-@5>nH9~aX-{;z?Qo5C@i zB)Ok==c~1X-K*&eU(#EGhTlnrE|45>jZ1IS@7AH-9g-C0%kAesZ*l?P-@+Np32I~m zl{qntp?LmVUN9kF6V5Sx6pNPpltxD({n~01IgjkW=5W0ClVC2zVoM#VTFqllL_ujo z!>6o!3}df-rpT6~H|4YBrN31UR1dcA;40^06?_Rh;n&=koV@CPlN!t}(6qVCTSoF5 z;1m)XdH16OSEjTUYTbcxm0kK&d!r0Nf}uAvx@!=0e{`_+QEh6?e{4NPq%B8Ru`G90 ziS05njN;a?%2r6lIVL3~<>0n$qV)%Tfp0b7i=>sq4av7A+L-FXX7Mgmsk&&qu(fWJ zN!4wOt9<1+>&ENDMHSGe2hxjfQx)qb>d+eY=g~KFm?>+R1`_~IkKv#hqlwA`?uw&9 z$|=gN?P5zKSGj?Sw{*DUY(8#Vds=%<$$6VR<*JKd0j7^Wp!MD!sv|wWMr)nm<2}Sl z4&PRaxE0cJRyA15AmgH}k{K00kB{tvCzcmi`Z*NPkw=N9a)ZG_KCibTPOd>vgdblr zZd}KwKO+;_q6NUg{yORSTIBA6)px~vZOPFEjq2|W$>vltD+r=!o?_P#)CA1t1kA}7 zbtS|m%`QFlC2g6~4)s`Tdh;WvTMdXZF6;c6R6T~!kBe)!N63PRmO~ ze>?PEr$pn%U0!P0`GY%TU87Qji${gfrgPe{(qPM2dQRn)k*`-WcQ6E|v#P$*r9e2> zOxi_#nkC*bWD>wl-(`)oG9&DGPXI+dKa??U;_R@Lk}zJ!)q<%HuhS$;5n^cXSk|cP zb`pz!(Euzqlh?O&w^VLWMX?K_e_8l=?+NmK#Q)3Q;fE>kQkW$vU2#vLz;UU-7Yb-z zgkn3Kl{v?#WY%#63G%D0|F;gyKl=x13)046DKt?*97(XG_?Jepw}>;yyBR z7Lfl)a=k1NZ;H0kjOJmsuUVUZLUPO}jo}v=Nu>j=e1v4aQI;oOXwq+Dr6#o(7u;`2 zIOYIeD_t5kbpEpB@om|+^nCkHsb4gYXSyrJnO#C3%d{6w9oiI6{`-xfw<+*oGH-RF`^zPjW41p(G_}w?L)HR>T#LM%-JO`bXe}MTnzX zR5UuD%M%{OS*F!bo+a;uRBFV2c}Cnj0^)fwyesT=V$jUQ!iggbaWU=72-ZIps-az$GTT;-_S_6I#1R~x%5ziZZQ5jm=A!& zz{bu!c;Qwlm+|G3_kib&4#xLiwUgSZ#KNuucvK9Dty2G)c@GRrVbs4og9;3nI9Z;! zT|BxL`Wfa_VB?I(|9zDT-l`IVMgOXn&Sg6E*@|BA?`=hl!xGo7Qg1Q{aPKU)o%=}D z+_Lu+^H#0Y9USh?M6Bv%ddOx195$+g20gicO8gs@r8a*qmo6JkXFGGtEmj;qj)=-e zPZiA95d&5f7%oc3kMVeZZnw%u@0$*=v$|lrw7?Jj#xoZ+$!OTZr6WV|r{f$K<@U_# zLPg$lE4nu~RKhQT9+?VWutwh6|Mlh23&JQ#yOh5%D)O~_Wjqo%;Tyt%s3O#jb<)MO zo2}EY2v75XNmhfYQty$0rN7F>F0B&;vKFCFby>iBtp>rw?CXLwkx-UEQeEkQlJDsC zWwF|BORk+>4c#HyzwgvPdP*E5M-X6q>L5ZW4mn7W^p%9bQAt$LzO<4%u#(fTvg@~@ zVp0<@7|QkAp;t$&`v9Ex!Yf<%4lj+z4LbiA*#G(Y=eW@aMX(Y1>w*S$wiNN+B7+5- zXDEGM&rJBV?I4faA)<=^_icMN$iZ_JJ8Hi#Ui#&eY=sUBWN z{{W;q(v(fOOp{E+stZs9hmvB%+NW z!X^qa0vrZ#%c6+y&7v>!gY;Uy``H)viJ>>AB=*^pYKM6HS`z=WtdH=a z{#SPHg_T}#ZzGchf#{A!-uFw_=R!o_{7&qvRnezq<16Pr&sT^pW@rK0Oo;ukkK?OW z%#8~j%{pNH?Qf^^0s_=ieV;PP zkXMzWJETU}oBT(yh8_1M?90V+@?S7Df7It<*Mt28CPL>;Fl;KOErk|_xA^^SigPl!FeCPuWB1t{lhOL zicxyY|7hBi=fWd_bFy%!)@lGaQk!T10HBhpJ5FaR$Y&8<6R#Pk+?rS6;-cF^!A6zw znFV@Pfsv2;Km-mkj0ISb&~RW}P)8TvNTd)Egi#QYh`6^}Ri(Oe#m3^|MN_t0=|cI1 zn|o=~B;$#z`4gDWqzg~|9Ppo9!dv@O)>YP(cPg2RDr^E-J%>FV)$p&E(DnEC4j?yo zVHe$UAJ;$bxcOpo0uofDL|7VfVQ17X#bN=y^w+KNq)^y^te0*J`OJQe-LbdtW<8sc ztUDc&XfjHZKQ5P@U^9P>gcT-L>z$nr4>iEWak0gC2ul(QlrCZmBO2!D-$B0vv_$q%Wy4MTWsJ~kGqglIgaF7o;lL4U2t<4bLQ@$i|i*<2$bDxd!j+SotHmH z5yHt<5DlC(g*in-860dMO7ZNR0co>|);uc3GYCDTNJ^d;#={&$8|x+0i=lLg)Br49 zF>z|Sg18QEvv`(it=E2a_iT>;xiqtQI(bHO*68{R{7IkE~d9X$N-1E}4tu1y0B^o%+?wpp`GvUdJWOW!6wEXS0(MtKQ z-w5cuu}(z+WXcv8?}Hl?Ro$U}Gml3>=*7471$~bQ?k$9$dda z!?$+ldniqp!HX$HSL6 z(1MCZXNAG~W4kEB5P&l(QyK6(!Ne;$zNPQsk=OeQ{Pb4HO9-4oyJMLSgC50HVWDCO zmjw{WgA-*?dz=P+^>F7xZbrZM^N*bzEA+-kOkw+xHc;_UP0_DYoOFKL>Cq~^bJ5Nz zfRN8EHrrL6q&%F=OnkVfBCEe&3`0|ZD8|fS zA1XJX90;Wz-3nD@0;XGtM@KUd6WyMK?)T$#3a?;CjWK85gU|PKxtl0&tmg#^2OcAE zVR%=UF9a2drQWVQcHjg!vzlWpa!QabR7rt~7R0Oifc( zIuT|{HP!kr__9hqw`jAArA^Op@j5lM9-k#>()}H4E$__4ZOY3dntX!Ovt*h`u=}z$ z`MFK}GaSBFTLbDlqQnU~$a(DO@fcwvDa+jD;sb~3zF6KM@nrE%qbyOrYR%Ss^do;Fx&UnBZWCk z)sIgg>JH=J%;zQ4OV4mT3L=42iDTbG)$HhK`Dj~SK}CIOeW`CF2dtIDDXDO012V-RXee%)`~YUiMLVx)TvB-?Nvt6 zo5QcvtIHI`tWf4(oA(3;$IttJ3OEIq21k%h{)#LCVONPk#_-9n7#RAyWF<8*+GGT|+5?lT4xu&4Ab{6M^q zr7E!>Q|KW?ywB#4&~PQyUx@9PHXMpx_q{i8<{*%3a$q<`X3^5j3}gf^Y)lm70=I+Q z9PrSIoGIJr9URG|BHnaH>;1iwuH|;Ps(4DUGCRAxNbfcG$1a5KKe>w$|sSK zD50%)q$@bZUIDMdMB<^e)XOlyp{TlShSaeeBO!3mN_rVqF^7HYvX(`E1Ki2X1?M@W zr!EyPsI~y8Glfy=m?WHxcBk>X@gZK8m2y6>g80sR4bSjJu`;8 zT`dB|UUjRvM_)vb_4M8Uu~5P<%jtXd!#JJ4J)lNYrOM_>l2tg%>mcy+E)+1`wt zz+EuB>jfQucxzNn<8ofQs?hTj4}azM7C{FnWBwbaw8mv~nLAC^2kx`+*G(_;wRUS= zr7wM&FO>IkHNzL^yFOd|A+DA~A-9OzRR{ntzXsH(Xe68LL6|1Z-hnG*k@ zzm)yj_DN$8ixqy7omHI7-;QH6@QkLM9T|yS(fam9yQjyEPE=F#mXAC;e1z|wb{VXH zEM00ff!U1I^Bj`D;t`i)jo1m2rBoVAuanNHTp?e#oLa!iKL$wsF0%&KkEvX>p)nH0 z^(Xc9I-!+n>zZRXn1GjIUsRDg6O&KV5BWEfXw`3$T(IK1pnqA1?XoE5HLlcaqg8QS zXd-JJtEK-|&uZe(G_%tFrdXIX_3&%5h(Vlo;B%?H@;oyv&BcG7;mY`V3+G5Sqj7Q; zQ8K*L$HDfLaLnfl^D6T8RKdwSKR%CG}iupZb2n21Ek_`t9Ni22Amz`al)Jbs{p)utw~1K5C$ zZ;yEs#1&@HQ1;i4b@p^&WF<>4h1@agY!R6Z+nes&TEv{<_X6UugYvty?B_<+NWU%~ zNdHMGv?`PM`zc7%-riYCbh6jq3U=NX(9`?p1Z-46L&J_I zLW-F#PdbI6?+Qz`my3#0wt8@iEv5lhaai7Tc<^LTvWjG%rf-zJB{YQL1CSQWoVn@w@DGbv{$6{uV7_k=6U$b(g@?-QrVPvCY%VRFadC~_;6OuO; zaJC|Jgo$xD)(s<$@S4lV5dQ)cN)mc^OGNKaZn+o>AYqz)DGoNZA?JHJ{2?<{AjmA{Cy;)36^bcSl~`3myGC6X4uJ>1$gHj?xj}|SRk+mJ zPaFG|){&wpV`5rqjK_JIg=0gq3`F_Bhaq4 zsuZs}uqa|#{3qd%u~bW9gFXNa{I`M7@J0U`{5ow*>8rxccryc-2$8`Q!&FN$qn)MIncVB=bwN4NAAY^}Kq%N#tpHplM`?=Wy4J)=XN zY+kuo{3J2l`i_;B-#5&2#>dCM;fJ!!mUqcE2g}{kJ16k*uwMoa7kLAA0&yymL{IP! zmr{>|ZGeP)xe-QJUj6A@uTys`%Wx&H#tYe@525Nde>d`WOU`_N7peXPe67oxP_j+* z8vr?ucrEupe?j?@V$v)zZwPQa_FXVnz29yUdY3p-9)$u2!}LXt5@<1B2jvS@aNIR!*x`zsrueUHZVv zf(NhOg?wfSc}F{-qmwehKE@HcCJBMRSk4lkd;#N~K-Xf@| zF|JC_bn4V}rXfm3HR{fGm=W>J&x7kUXSyFHhSb_!74 z9$?#W&t3*3BI73Ewt6Cfgy;^rv(%IuDA}dS(>be}n6O-2mQ&9$>Y0dDCQJM$@;bnp zG`eYVC}4qub-o?#8K5j`ohvmojSn=)AlC}{Yiqt@c0AhLKaCxajIotv_~)%)R}A>P zg%Lkuf@3&Lkbck3Oxn3C2|yG)u$S@4K_RBa*MS7KH=l}FW(oZ>4-neO1uAWUKW!uX zG#|X6H6bbl(mzkzE&o^}1~e6mSBL^2SDW|dhJSUDwpid3pgKvfSIY(S=i*qZP*F@O zsi`gtS@+kPq!y~%lvg~rm9%x7Gt6I8kq(617mLNAp2?RCpkjOX7sB!S;3%lqnUg#8 zxaiG&uxBs&PgdHpV7v5d~#b9XG^;*8uP+Xf1q_7Lw z2x%0>dI%!KBi=OHB{SIyjd-xZv0;#YO}G?rXxL2gW+MNUjn>#^za(lhja_PtcLk;$ zwvS8bhGw&L1;2(~676@u_B{vnlgg;L`d`50-;yyl$B!UsXvltGm3^pQ&5^pI@!OG2 z=a6;GO-RGwe5m?RGL(OS9^mfAI|a!&Yt=soKOnNuIk?dOcP#g_4q_}nTG!Y{Ad6kv zd$LFBG2A~(>I(NjFXW=JhJjs}o2W!og@vnqk9KX(^-4E4 zw*)Xq$dlMXMnT%Ng`y-H55=aY_QCFs1(NK1!CO~5HJfzp@(s_4NVmS3+~{*`l_-K& zL0Lr<&m{ef&;F(TgS@DyqebSr=YAN5q2<2R%>?8KjDfvC3Qbv3grLqvkM#2z`*UVW zW+vBB1=Z{Z$W>Nu8FGjLy>E3wg!soQZ^aH=@~=?naonmYbbA%7zsFZg|)lA3?(-#VobJrQbrq)<$2=e{%bOEkgtyg4nWS`MokjJA-^IHL!&u-n|)` zXF9XEBU2c{{|h9vg2tf?!383HOsCG)v@_(7gPa-Q0&Scx98UEoaFRIU8L)y>bSYUcyVSb2cd!uws(<;~UUX=iJ^nPV0wK|}F z%WLOTAuD!uP1PL|3uv50li_=L`ANW#2>U^F0Hk^{>ZLe4A;mX;mj?x1&5r)FIVoGX z@JmSGUqY(_+~E4hRcT3@*pxu?#GZeG$}aWo{!vcA>kv!;7Df`PX`1R@)_erxjqs@S z&8bA$FyM9@3$Q;cD9D3lIGBTAMZ0ecU>PshyQ<+_W9q{CB8V;T zMcYzy?f9z@F${lIrTCUlhIH`k0Fpc^yfL8}G~IfU8#;Xo%7_n&VBJJ<&SiKFSP2^_ zMd&&(8lqj=flFYv`@8uvsYkX_TCA;5Kz6KeSPdJAx>*F4e91GT+aYI0c)BbbNe-&&TR$pZI(3wk(P1 zzWGpM9VLHaq!K1PO!_z8Y+9QDa4(iE@@M$Y(W}7y$4XgleDO{uu}kld57!+uAeVE) zfl~dSajMc-Xk&@G4X0ivAB^BC(UQt*;&c2%PbHsqcy%K91=lVmaDMdy5crG2PzL&> z`@A;r`PG=Svp*=x^&0%nywmk^1H9ZMz#?}~dEi$>w}!8P_>WWwHF!O;s48xT0esFpBS0+-hIbk}@^7BKx7NKo8`Ks-2bS%X`)Q>ZF{(z2^ zy4}70n(QI+OdV&>19f`emtyxtNn3&}fPu|+h4qnC*c>Ca6?5lllD>HK0^aF%M>r)| zGR}lpw%I=>MeMz$@#Nm#PdE)iw88)2MJfY605R3u0v<25NF9KJ(__`g1mF2a>tci5 zrvPpmicAITkGC4l&3~WjC;AO2XjQ9i>^vYJw;E3HOcsTFlWsSt`9sRM3E4uH*pD=( z+^~`suIQH(6cDdv?Xnx4>YAU!%Ws|5b*xl)IZ1Y|Fr}S#*8e6!OBdlWA_73BuWWbm z-hYHf*6|aNKAxa^+DdS()8-)m<9doj%K(W=GQ~;dw@=X6IS&$J91knvGN=qHu#^dq z_&B?W@QoAfO(YStmrFS!3~w(~M*rwM9~pSFwf7Wp0;F08Rts{U70?V2wjtW8{VzCj z{5LqJivj3>Si9bNHPBq4Wk0Jlk2QWYB;OuKrf70^PD4+pXc{t!Z@56oOPO$q%Dwk7 z)d$<84+f=kXa6*+YjGa)w=H#JV0c=;+V?$bKYHT#6-?N^`>s2nMYv-goHMK@W$J5X zm4yASYXG88@FP4i>@K3NpOG~)gy>A|B|Imr@O^FpZYb4tUZi1CNr;b6K#;sG%LCo|jttA`AVEMGa%AZONYVOGSOo0{ zA|SYB0@&&%?Qr=bV#1L*I5Ov8aR!iT!vKN&LJ zAbT9p%b+o2+k@+10;$mHfhO*}D-uLN4xMo`>ar`V6RQyG$A~x+{m#A_FgYXDZq%gT z%-6#p`&($~N{a=jpJusTL%c-HJ#;O)n=9iyokDo<5P>Sw#_l@&S2@lBmDPDB+Vf|3 zu6_jNL#Rx$IR0qIR>;6mm?!f$m@F*+C5hobVFQtv0>L`4NCnYMkoZE7RM@sa5szb{ z^)MBOd`Kp2y8w@b;C_&eU_-PWqq1w|;|%04xjr`NB4uyPRq1?Kf5eGts)f)50~_%1 zlIqnv>r1=C5M<S*%(%RUt;ok5el>vUedRr*j9W8l@(XYl9r1hj6I<_3eti z-ByRQ_3QXUjzH|#KwTxA7}3q#U{f7QX~l&4o-pkY2Or)09EO#1`nPT^D3|HjeoA=Z z{Cl9q4)~0m4JOwOq?1*cY+D~W!VMQEUI~(q^~FpSAm+Oj0fGvU8Ete;6vP?^9jrnH z@kFSw-dVe3&r_YrBcKlCg{8uJ1CR2rRZ0W*kfIV7srlt-Lix~|@$dFyT(t#8Y09or zUUsFKRP|k=>0%>xtrJboV ztYNDa{MlP;fAw|C%@;MF>~<(^-ZiXlw5k%!C$G>6m|V`163Hqsph>8s&xTqKc%n-z zQ~`=AP}_^H1}2oz(^{NUkFjx~Vu*CRaQ=j|}n|srtWD#39pZwyF<|W$u0~zXKt{=Fv z28d#Y<6>zzsA#OayQqRvb;oRE649-FlgIumobqz-*Y7!eQ>k0D5e8TR1;?M|ry&uE z?2PJ*iyEmH-W?}X%;L+}P0q2vYdjJ_e(w&Sf84i9cE1_IV=3JgQveYVZ@EW5XT2v> z@nSoY#Ne_N1zy}SaiCPGL#12^uYwKB>rYIRTL#@C9L#a?Q9-vh6TO&B93-L5`nN%C4OhnF^V?eXY@Q#Y24f}Zx|br zFbez&p6#R;`;~k6Pb|7+4XzT17eKY1L@+pRj~D7^jT^=Mt6H> zxFfe*jqYjRbK}dF0eV8sG>`4heYjbfpM-n`DkE~l*Q3%i@N1_jQ zpfd2M{Z89=1>_H1ysCf^T&J62uGEUR>f1<{QU<@SL=hfNq)EYw76%8mhy7!_QdT&N zW7w{22CjL;)KLW8BLtHe7#JN|=DKcHHaH))Bldw1(zl9}@?3QcweQYbzg^vX^ONA( zYwn1dzicRL&ong9Tb;^rsZ*lw=e6I^(Y5wKJ7IO3v!%xwjXvt{^WnXd6IGmwNl-hN zUn8T2fPkG55Hy@%EWY6pR2nlLc6T3_{m0wUS(<)-Ts$|bjY-6s-4f+9u}3Sj9nU3Q zQe`Cb1^Ee@i2;R1NXBKm^$8sGQH}t8>E{4f<{8i*P>!m=(RebIxcy}qTk&xjR+PRDplc&)Gt%Q$TG$m8zFe$ZZ`;3Ec3#>FJ`mmafXNGGSPk z(rk{#)zh9p!OLLm(HL(Yw=dZvD8&v1(=7OLhjKsnPIBKS$n%5fOI;Usdgxe0LF-GeSR2XJc6_f!Kz~2H~ zTzu09csL#lnzVdjpI%0+AC(02mK=GFXgH?R7KLsrV)#gD3KJo2ATWL6a(Vh?H}100 zOU&?SsY3Xh%Ne@5?vP@WSFmz73^NmT&F6G$X8>=*F7sJ>7JTFEarw}~>dX_F|$?5reF9$d`hK$ms*$bu_7MMWm8hW2N_U)PEP=jUbt1TzzSdth0> z5M1+D$m(JE_TMqwUj4ojrQL5OwMb@CSV!-h8II=(Alv-ULPdTfATS1CoZZAXDv6n2 z4SDH5{DmH3YH`F_ZP&=PXd;h%>& zigr`fur#oy+yqOI8`d*EQc{0QolqMFQNytiiYs-)NbReFQ6lN-dpQ@=GM0+~X%po% zz2F#tSy9?s8BcWW1q9Q~5bKkuEcv5*Wj3CNc$>u-fh%jx>z4_8iFNel}pbD@9h465TH2WX;`d)N} zO7));7V!T)VVz_3Nh5Nn3#FnCYqha7PgfYzrW@8q@8R7(MQ%*@A)+3i@JlN;*eO=c zfF2L%j4*fSMc;C*4af!&NZLy2`OUJNtB*RN#!R=NUD8oNcz*G1F%nke3%Yqcy0vq7 z!2Sq@K4{|!GOAuf(?9Z4wT6O=LIHU-@08C9)ZcMXYZ^)o^YIAQ%S1tJ&Y%opoc!1g zq7afOr->dTrOk}~2{`cDy|hph2P%3w z-T1C$=EUI*9IS5NDNeSP$=pECFx&e7uYEZG%RY+Gk=HkoFxRuq(3*fsP>9OZ!NC7r zz?JG~0Z*B#E($;a%3J^E^r?Bg%q($^2Nj5nbt2Md@f43SXctpu5;DLM;XK@>e;z{M z7AK^5CG9y{ZnmX`M&w0VstrGlTLDYSnJ9AXz?2e>*Pv`zp`uZEPgfB$o4X$thp(_z z9UA*~`g}|N+;^ZGDLK8YPkl5%y05cwzGnJ^i=)Y1319(F^rlOx;E%D)Z@skhtAA69 z6q~pEQl$~r()paTA1L6u(#8&*L~$id&UJGxCu)~K%Ta3n6Skomy* z4QM4+J8<@pW4-6ch4g_)D7Al%3X{Z%M6`Fa$KjCrb{4d0TBXrWOo*IIVAk8^(UfGW zZP}MWS54Jbm?l?^A7x$!{`wPp5S?EoxSH)Nj8Lr6pFf{~Oac{l{*;r!KyFN+|ZnoBl%aqJ~$ujXRZ3oCg&@jHLjRtS8)w zMA$_n0hQX2E=)mc1_f=sljDfKjL14ZF$Hxv?2-IT%7B^dEDi#N9LAW2;2KSqPJ%2} z>L00xn1@lqRg6}YG=b`o-e$c@+r#$ajs2Xx{UrTRtUCmB`=k)=^51y;^YY#!r16r! zMgOrk7$-+63IjZ4v#1`x2lT(5@|@tQfE=9vXR7K~g1vPh6Y}8J8})5nAa|lg6*x+H z7123i{RarnltLP_*jul?VCNZ68uE^Ns~3ZZlb#0Nj}7_px0*m2LnGT#T(~`l#vfV; zsS3OzbtS5E(cd)lqg;BA+Ud>Z(1)!Dd)KD0e=%{FsMRT0)nC7agnL_$o(jygXvQ#hSZ8%tg9~J2; zs`3*X8OcP_Yq)t4{DlO^bn_Lt}FkzqtAK(bBnMCNv|)IcZz6jIcQ6u|jQ zMf*d)^jJXb)av3kg3F4F@J!VW=W@;hs$OH~6|~E=W9vsMNiIApIOn&_P`=Z5uqZJE zpaQJ|h5qCIopSk$8|ubCgu_bm{Xb`lkPH@?(KFOFREO6zkJ5D2lwQotpR&@kiMIYW zH(iQf@8HxI>Zvu-?pAZ7S7Gi~i@#}PNkM#G&OW%@>YoPE zt`BUnHC?@%zrGaz;}{VAiQw-=UHsJu+$w9O`qTUPQB0msx%t;|d{HA>i-DgwU`n{^ zY(sop!_+yio%!)n#;K7%Vny@c-tgsmlb{R98r(@S^|yS$5*xXzt>G~GqtvF;HIZuV zI=td(JpETy1;iR+@(TlAX{cyhl6Hw4wgu>@aBLm|mAT4~`~D)*5-I;2A#Bw!;5bY= z@x9S;Fs)KAK$PF#6aE3>6z*FGCY?Y^7CitJdC|#Eh+>CalW5{1?KGzPSBtBBPm1;( zV-PQYm&Q1x5M5M!LU-$-0oy|CICl|g1F^3dQzo8}b4RA*sGX?Ui`Q$&Nx^L#Y63NWs7q?oog1YZN?<|W z;C-LLx32Y*4qg3{t7HNh5!owF`@@p7vd`btekN%rEbk{p&Ic9od>^KfUy zvEI1|kJWq@o#=jQQPWF&lO$qhGO5dt@$9{dSi!Ym#^I)A)}fr6|rtI{(_d7Vc?9x=>5_oh_j?|lL z6iOf~%l|+0{C|PpE`L!Ba}5$@UL5&Va>EBiUIMp_S?sB|*?zobtu7#hF_GIjOIk^7 zNMz}eEbLh-2tYtjn@XfL!gcq z9r`*$1i?eb!KQxFdga^yf-C2LfvY&Ee`>QCfDdTcUxYd*^Nj9e+>!U&eq3@69PZtd zfb#f*b76IG(wF<#@*b|Vpj%*UatG}w|Jxvjqhu~g-Wdor76+$NB*~XhDW=~@*j&N> z9g2~lxf`xq`UR>WD(-{%vm$Xw9~RQ|kA)SZygXu@h+EMEgJ#&kYzi5-P1mckOpA5C z`S7$q-w6L&+EV)BwByCPU2+{cCXk|wc@=0DU=|@z@}QDSNsA?++J+&D=@fBE$cU1R z%2H2BlU{_CdA3QORSZ2JKKu5CL6DYXSGTQYIwyruYi-&X)^r=x@st{^SVY4c4Dy2r*DW`X}1cnL8yXH6HkmYY~&JlkHteWQz z3!;IM6R8F4%1U(Xij)zBS-ppJx%;>(WNjfT(vni}5CEBDq7 zm}40*oK5o2zx^SMy`~!fT}A&PbPL>qt2~8%l#h0%oV0}&RFA0}pCcCbl<*WQkB^Z; zP`RL%H20sx%O03fta)7ha@~ZQK-^5hXSR%O3@`PL-G!r$;EsSiEU{*wXw7vQTXh?- z8(glq#eI-Qa-gmUeE!HZ z!L8nWYeB{HA!exv-s~~7M%AS@`uk(;0gSwUaNMqK8+7aTW4A5o6A`pX;Z^U)pWi@p z9tp`~^suoGmREtF@KpnKobRX>)NL5(g-iLdL&(ttVu57@oFcEpCjEH(MO!TW7ChJmvY&s&AzKcQzx$F`LN4~27* zPca0+ZoOrwr&f}j-L7b>;RHg8`G!e7j(q5X0&O-f567xhAhr*A6*CVO(62zGd#`)& zm|ZMokxH^h5PJqdsBEPdhJ!QawKGg-Y_e9g`A+1uX9p5cu_5tIQZsrp8m6oQLfaeg zfzjy~Q@}Z$s-zPBtA(JtTQY1&nzS{$BlEHp$T}M93;G@{IX8_HH*MAcJ=zs2AeLKj zH6xgh#BttDpc{nN0z97`6K3egAyAhA=0 zqY=tbg&B?!A8uwQXYYEX3Y$SE883eNb zo&{P22K7;=sRM`}300kVgu$~`f_H4}Bh)WgI(5PhgkkrZfI6yW9oYq#N34#WA z?N7+nFb;p+=6o*$z*9b^e_&T3FwcHQ+65=OhpdDd9Iu9mm>|?pEy4lyuM^$2#s1fbm39eRG1TbKGJH%4j>>POtlknQDVaTqWu@ z5VIbU<35?=KJD&6+!?nPw{BOY;oGIWkvHL3#9k~y|1UeG0d;h~WH z*sq{ZYT>~ZSv=)Xwu0o}cy@l9@}U(7?tyldy_kEn+p8g0{lF)`eZp=ef3u4~fgE6G zh@k(O^E>4IE13Yo2M3B{3x%{#j;OA$SAq_V52@jNO-y6bs0YtDJv=iX9Q|;ug0L2J%x?yCX{@36Gse?lI9CWAAECb%CFda~8QJ=tNm+f6;0 zgLsn&eIM#21k)(DEM53Mp)TO&OCSPbMR2hB2HjI{T)^s~>?0?hbB>Au8y1}VN`(sL ziliUhq=nQ8-DP?4_S)O{Fbr6E0p7B*a#45Lanl}py#b(%Wb7Xe<9_b`YU6Kn31CU* zpN!62@O>7Jney4~qySM1HApWGFfBGY!r=KTjJ#CP~$Z??YyhZo?vhCw; zhlPxfcKxYU*KlyN@RkT@vAXv#Z{VPj^#$`H5A`s&?nGuRZKvHT^ECGfZPghMG41hD zr+Xnu7Py;j{myL&i7>)!j|;&y{uy?Ao|rc|&j6XrZ$j=^pu>bz&6iRrkS7*XY+U@Y z&N@Ji&`Q1XwT+Cu)<8Cwb4*2c`PU*D{wd4-teNM{+dg~Ap_j$<8=q-t5)R$A*yqJv zg`Q4o5^gt0C8-SnZ=$W>MjrLo`c|^@?7D~!|F1C(}A={mbygx*z32>Hekh~x)^i%KyUU%*B+HzMuKM2nUYUxBC)IeqdCBSxN1uYuT$ zl68TglHe)DgW01wY>>rdx<|Upp6$|aQUL9d;JMJ_M23%7X9+?tWlHMv@$9T|EPhhF@XjD_(2g67NELX(u81R;eZH(a6yDRog1jS z3WXQ|SX;|iJEto})|;^&yU~T=8zK?vuWytK<;jb9q}Qoq&cWqsF^zPNuAqGZ$ zS_j0*Vu`3g=Fy1$?gLO;gHLe}K;-Ok8&q_HX3&5=;NWf&tG#c@w(NVurh+w-Cj_B= zrIP7B3dTF!0?Br6b5k84uXPz@T5i9xgDQMUc22+adjH&!>w>SBkio{jb&>6SbN>Vd zf5iu#`ResPbUSBL?c97_k*z!A1OY*x#n;eA8n%(bFn(rl7nl-gtx^q&l90=LNF_K> znM6U8w(ByB0WJ?xAx=XLQpy$ZJ- z4P{FXKTScPNZhd?q59l&6iHI|oW*e`1!)@>&Eu$4GvicgK)>L=;B{^o@Bt^}6=mt= z2)I3jjWzp@%9<9HeunQbF!WlAdaxLzFthMmH)>7vD8*$h0r@!u)6#h8Z@O_a2~4Gj zBnx?ImL3qyX46}}G^_vUKAvygkj=byQz7!rgCj|(F+Lw?{iWh#(a$A&x04Zvkv`|1v)ATNar6!;S zwaypTxTNnN$uebUt*g`Own6j{bE~=8{@1~yDsVDu_;$|!2n8)XoX9H{L^~RvtxokG znPFq>7a6;6zz5y{B_SgNi<8-3@GsIrQVK-#txn**Dqnzg+z&`dQe_~Srlp@po0O@h z^=G;lu2|Zh_pO8D441ApSV~mX6Xe+Cm6hcQ>eW+Q9U#GlSim#DJ9yKtVH;Zw#{Z%1 z;m~!_^)hrhRIaP(!fhnE8OLH*BQ#{iY^QNe?}%yWo1%16CbotUzHoI%+XAxqBV{(> z)+Aj3W#5OTo*m(Spbc^1aISEcPw3#l{ZGf`>ahIm*P|9^sNylKGRA&8O0r*!vd$gEqHA*#{V#p@g)P3Yi^4BAuAR zQJxEJtW$y8JNB3e4k)2Bt2NG>8=5Tu)28i`Mau2j`lckn9FukWFgB6u z;of-T=oL^wW&s?lA}#xUI8fw~kf-_0jhK6>!(PnAYsYu}3BA1$tpSrVL1%WqY_%iw znkM|0V!zSo3>o+cYt^hh(ebAc7H)Y%l#A#Xej|Mz-_#ZlW> ziXw~`4bHz-pEXR{zmpw7FWD!*Z`N63hi-bSfGzet)s){7H2-|w2aYhmcIw7?UlY*N z|EvgjNPJI*PJ808g9L$Wx~7NzInH1 z#lX=MU9WiQ>>y#2EcISqT~2-TvNKyXlOBVWq)@A3zXPLQg?J7VRrFaz7-f<7xYyPY zVSCj&VD<}GiDCWaXh<;Wh4TeP4I7>3oicbY_%1$Ez!e4sVBz;>;m1&jo#haiMAl9Gg+ zD2l_`EUBs-VL~CR^s0)eI^(;8K$ShGe+pNq{c$C$SkRJ6RG!~(S1CS6CtOh>)KX<1 zvLOX7&I=#&THKDzhyD?T{u$!tz1>Y6Vx$@-AHLh|Nst*h;zY{8xR9FR0pj>K3dVx9 zf(Jg>-gxi+jnihhBb68ia)!@o+`@6uf3MSKC4GXD1=#D)NX{P?jhM*Tt-UM33B%Jm z`^TkRs?cE9g-3&urqL-DHZw!MVFu(w>%It>QygXs;?4?Lp=!*Aou^j8MYwSb`Xc;t z=AR-8Y%8yio`J!n*XPeB%7Seb9t8yl_qR-HLh>)HDu8FpbyDrp34N7rF_Q_u%K zT({GEX}UA4qK@1%^{WxRaq48b#|G&BKCJkC~&!9`$MtWIA)BYmUJg) zHTf(8bXDxT<&+BgWPuBVHg#GXS^+>!B@H^!DE5jBBYtt|@rqIZJPB>1WbM7pD`TS|eRXw)b_YdDx=C;XWZ& zquj?Wu6}l}Y1+V9$ks!Y;o=$7Um7-R1^5m18hK(zvIUaJ8F&}a@g2R4y7b(;Y8)8LOOZ{dq`Z~Fpq;F z&Ij0Z1vx$Ac>&*qsm%HX?R<%7L{Ng78rKK%CQ$<9eGufWK`xX+>xF7%^>xvWg`2By zua`l26c&HCMri%5l^RWP5t&p#8fglVZ^l|pzr_KRBo?7@NP*wl7rMpuW=b55aW(Nn zr-n}s1G`yZwN{F^G%$MW*ns2^LuNLCC4#JHu>_10bFe&FURH=hg}5x?m&ONx#cZ)= z2g+YYY}VSQb+yi2Gk$vJRcG-P&Ak^E-&|#IomziYv(~oFm+!>rwS&idtGaJV&dq%D z=6_^Pl6U*cFI;)!f|k|YsnE#Q#;ccJv3_=PUVFTG!Ol6QL8EM4QDtY@ZLdGxzkX`W zukEd0b=!*{?0Gc1t`~#Mjz~J1kCqC)Kk~TT+~S|j_2%`uU-JLef5tDjSxHRMI;+iv zNwt4VR93CQkgvYgM;mX=*U;MnFa@8C=7Z9DL=iMM-wIr~TwQL=m- z&P_T=P?k~*`f3zKBu=~pDlGw}rDZfxR4t>mptNRxU5hYdQO5t^$|DWRh`l&@?>kt& z?=`H-ygmxnTc<%TyS8KTJynfC;g_5L^5uWXes01hY-F@D)kn{Nh>6VaGCyYe|9;dW z^;@!$$RnV)R#YaqxK2li|ci4sSc=xS;17CAri-0yz(M@x8R zfwwbXRzvKVHGf>0T&1-dCzVx98#8|ssj8vhZf{I3n&2|0CS5$c4IA#b{Q5GthSlg@ zg?weh)XCBug>{YIL2m-e5AHsh^|Ey~U6 zEv7Bzmym%V9WJ@Tj<7|kveXzvrC`-6v2Dx0Jkjq_ur2-Qaud?V^i%Ng)q8t<^ z{-{J`%s%SCjv;)T05$_j`ShgL>E~IUJU|8lI)4bX_bEYl75gzuutWHxLH?ls0I?7S z<0l;-oJxP-_*@h%ss&_9rC_lHcoJfN>$eI+GFlK4m^K00|) z(@5{)T2`q~WD+rrvzl+p%p+dLpW^p3!OT>8TxcWJdHUFsy+k5GIm&FsdHI)L8o1D3 zU{c`($XS#*n2^+e0!BMQ;>GB0VX91P)^=MqX|B;8*2rYq<@TkKOYMK3TTWU28DXd} zaytEfI_y;Xd8#PiU@W4!z|wf}t|H792}P@lUN54GikwO=u2K^DafibxcMsu}Pw8|8 zR%5|j>kzTz$x?rD4NVOZi=QYhi61*D%Inm~AyJTk2`Ls!LG)vw%ILZ2{+dc(JF)($^(PK2Up?c2%;C)Gkrl*& zYi6z&O0dq}eDk`iFZgwWGjjL`0YbepZ|B*L|)0f}9ZdbG-lNhtV`}>m?2y;DC+`))*!h3Rz_#D+KlSBpERy^DMU?Wyi5bK71;Mr~BuI=jEw70Rh z8g}OGwhw8BTzXMIP3@9?w(}Hib;kh;2y5JEG|3%vb)xnKe0)y(0{$`W8`C>hl*`ZQ znm6l+=3{@+Ni~RxqRDU8yC(ZmmE2TfHNNb~llzZn?j2c=f5;!d|Et>WZ>QMU1g-Z> zBfq`SV_*_54`p7?eD%9OURi-LUh&7fPWvpiXV`52VGhz9=u!x5!54*akM0uPwf@`u zyZv-|s5vw{v?SCYqE!gGh1z|xuv68}BRjSa5p#c@G{gim z8Br$b^f8;GX4vKsmBvvsl5*H`R5l6p=-Ah_Ump|cD~8h}!{Y7>&iVu>U0QaY^z?io zmL`9uMN%w?h@P+HKVq&eP+9G3r|v2VJ@fs%(mj`RiG*d-p7A}0{<)xX`pDlBq}kh+ zo0yqJhi?g0WrD;nels#@&y5+jF23`P%q$Veu!LiefDZ9!yP#aJ#qC;*Y}h8s$Q09M zr6uU|@;FxERI_mi^ zcOqfF>cN$(M?5(xw0n#)kizqKZS7+R@TZHJ*pzh1vFatCpzerJI$P~qg-ea^7cJA_?= zf>$_|U23OFCE|{}(O z7}@0pL%8e7GB&m=3qxgz_^~r042ciPkPvTBpvfcptOF6v{fdjuS0iyZ(+uAB`LV2> zkez#6E*6;Pt1R`*A)f5utMGpp<-Rm=po1%~;iru>gTiHOx zJI(L}X;2oBgT*Yjl2*GY1lFntAsiqUBU>DhxH9#*2rJNk==9`&*JFQa(+qR7gX60G zbBSXM{HX;CMiO{&f-rjLkRQ+S7%3Q0gJhur0^*{+@Xoz|VUo%~M!9g#btU%LMi)#c^6qXn*LG*VEIr>hYA zX7@XJNdw0EXLZ_qdwhR5JNQCO2mMr*>RC2sC@64kb>ly|{}dtId3n1GP)(kzAezTg zpd@ZsuI#pgXvCf8(z9|7Y$ZugIA>`I?~@B4A!g9lzz^Ya!EkVc53@eOH^WEz%5>+e zjN6}oat3HtgXEhEqgt>*(|yz?Fh4~SiRph1TNGv;ixCSmP<$db zx`~yiHl^fO9QxN~P?~!<>zhe21sJQ=oUs}VVJ4??iRpZGlItM$EuDZ56gr!`iK+Lz zk@=A5GllX;rluP#uFMUIYTt~J3v+f?R?`&Y)@3H(=L$ovkOiWeJ2UT`Ic?X#aAK`X zlh5Nf5}B3lUKoF07*ntrn0YX!Av7u!{fu~L-kIg8PC(cYF_a)>t7oWMfuL;CYmZ!-tPfw!mI@8%bk4|Lg{pkDj zT`;@6fB-KEjRA24Tsjx)wI-}f!8hBXc*v?yDU}MvE(4gWa<5Wlv)U<9#c9Nt7drLr z(s+3Ne#R+3L96d}f`?_@$2;zJdc-lx1IPeTdOd$~i&epq97n2EF^gd3EXa(_qDXnW z14JrL1o3l1R;cNeqXrBYSfaBwN~%_ZQ!E)L-yu+QbaqF#k_8tm%2H!nnK@AwVo8+x z#95V>M3{BKxYhOZ3W9i$IH;Vo8Xs6tS9#_#tt0=cOcC)`f}DCG`EqZ7PMo=y=^G;8on$LVNFH}osT`uss>cMyT1|nMIMjo_7=(@xC4E2osMg7Z0T0t>#IV-a% zob(syEBY_;zo>uEeXsxCc*^>NT_HzunvpYV87ng>Yzo&7{SM!(;}(U7^mq)8Q{)dyVm7&iB*?i2$u)oA+LS#?LOItVuF#xU+>HU(U^tyV=NpKk zNTa^A-c7Wo=+?%m?5pG^_CUzy4xYtZ?QRIC=cdH($-Xk{NpKcP}5q z_1J~=xBd+`<4KvnZvX4ww`cxNFMH{D=C_&O9)IaiuVO#0f9(%v*AMCyn8!-6-0pu6 z(yh1+_d3>@`z`&B>%c9#&T)cPb`!T-b~?TyPS{T2lNLr>p>p!=et=_RJMXS=`ux1s z?c#MZrK{Mv)T6}8W~I+4i2~8L z!wwB8(_E(^$O>t?P!&#vpLbR`J${~aI=oaUYOvd^OO4oQtTEVZcDvJHa~glGTBF5b zH$;0JjSfpF6j#7hvRRx~IX_oxjYXWhq^YyFCDvR3Mt^^H9g)7ri6%%^6t>~YlxX&6 zrwf47CORH1OqVh#l#`GxipFG=#i4V)IU{4T%fpy}kmQp~LgacT#oWEb_TyKbx@=xA zzDpl;Wzw11$(H3KlZc~_bCrKPrj;h>hSlWei~Tl5B0Brezh2N{8aZIo5yixY%*y2x zsRZ%jvKKCHT$djo`5xrfJo+i+ptqq)RE7Q_1g(Kf)R$`3Ye9O3ht%(?zS921aLS-k z7%nvsLWQ%k3p*nS4H3j(rBF76H3Azc6zon%m&+M(LLgYg#EB72ILdzpls=}6!BsvS zU~trj!vT$wqC9aUNyQ5lRb*8a>vYb)jls;4yGs`Im1z zJay{3AN9Sd^Eu=-mHDsT+Oxj0;&*p0sagG9=G$>sjSFiX3a?F3jsJl^`t0=^FPvKy ztf^nPV8JhM$5cbeo2mcRCw)^pi;Cw@Djm0W_pdU;R-2BZ13rKHfvgQiD?qQU=zgK3 zjqauUO*>4~X7dpB67{~BBCJ-VmFu;Pp$8FKl~bdZ57@ESKE=Mkz6+L(A!3oB7f2`f zJOM`feDSy}K@mg5a>2}~-;nRpyrF;Ifa8YwhLwgr1`5D#`98toWiM5)s+_f1G7Ca$8ARCLOpvTNkK(i#~+L3EAa?{&VE zN3dXSwJK*hA5cD%_ZgU<6{1gXT?kO-)86egh$|n@a}|HiKs(P_wG*yjQEl6Nl^9uBh&AidH0?Lj}xmenhuMc zv3$`;U*>=Gwry{&dV+X-ZAzW+ptIjxz95ex6%tS#SP5%eYkO<&G(SA<;ab|ln?tT~ z`I-{($DtZ(@QuN5UM`{n?C&Nz{Jw5K}+C$-2ivAF`)iHMoxUz=w z7_=0p(_-xet;%2s1Q=CoaaoP3uFe->i*a%B_(*@H&PPVr2;LC6DMCbsh)zK}UM>hO z-%8;%;bVam#4i!!>tXFnzx5$I!k$d`jhuL0lv^B~1_ZFn#TD5AU$&+d=ES(dvsb(&#tDSKRKCsSh2;!(I+S)h1@vZx&-8N@- zzDLa}wEBE&Rc%v!|CaF$`!lS?qcBCAT!u1t^-SKftdCTJF7kkn5tx4(SZA*lT4u^; znl_vFTMwHKn_seiX8O*g;7op7G_PCUZR&qEUksB|&hps9yZLI~m8bFOTnf@-ba z)V(9aaa%O=?TeYru#i|weJI2fCF1;xjQ%fjDd?X#|8@v7gXX9=-%rlccX&IvS^O;j zQtwjkS@YB07fnaKKk{mmYZf<$@8))!Xf=m}jKgV>YuG(BrfCbyf~HwoJ*r@k@X@rA zs1qFA-<{YA0@xom3M&EYMYR%VBJ_V7`tCUDIQd~^O+2SkS#^r1vtPSJP3lWamPPiv ziwN0gmq(Mdo=<<6X|vI6 znvCY4SxmcT7TccR9_g0%s=L|V{NBi=`FEo`@*ng)xlhs_zadbMw)kLPtS;pSt&AzbIh8>G0DQvVx}xN z#fYZfjtMJ5W>cUyQ}mbnrq+h4Q}ghFqNc|dUs+W&sqOZUX7}6hxAuiQrrvt@l$(j! z*dDfx!xv8uP$j3kg<@G;ujeAG%nmwU~bbIAWWLX5v|7 zyQbaNX78~-V^A542BX%=H7k@#t=kaR@ z;1DI@=Rp!9CqQlbP7a@E4~Q$0=*m>$?Ebt%Jd$7)S8>jg#qpe$`1OJ8f^IB4{Y2Mb zkzdP#~lAV@@IOuA0!_ zFunZAn|9xWH%VtQz`rJ4^tC7qeJww%#B!xVWmDQ!Rp7pb*)S1AWI1w&tNkQThpkS7 z*BMY_j$Y;UFfv&bd&R3Z zeb(Y;Q$O)*`LfcoMEQB2ClhB4aq+N8*0HTE@9&Sz{Or<)o2!ePVt#W;a&WMwcjQPh zUD!(8*)i$HxsR=!Q`scMd=uq%ZPkLz_Z=f!mliR_h(htqZ1PPQS2c2?8E7W@LD;Qx zYF+9)Ra91vCVPLJZkH?18MTzNlQ$+bP2pML*%P|L{o(7P*A?7QzPaMo@b2jSiATyG zso0k|Q2tNmTd=&mMK)b)i^okhS;yCoBO8Muvg#fyuC?O073*O29&YkAl7XrsMb(s; zA`oMAfhoF~%*>g|kcWuKyvCbAK_)d#O3s!YT#&@c*>!)9H}k%T^XeIO>Vvx?1F)*i zo)tgzPI~wxtX};iwhLI>TWQcc8?EV@saJs@R)I#+ZPfyuWKB~ zb3V@bfJ|7%j*CbiAcM*BGRdrz&L>FAo5jkR`RqX_7#BXf9*W0IB=t09S3FqiI!x}F zldEbbOih1YylKPg30p7h$qab4_I<4#DQD4+mYxltJl}E&hqb%I(We&N=rJ1IoX_Xh zEp%?|o4oylwF5WxJceH z8}_?&D?&Xr2b+Tg?R14aK8hd-!jt;dOA|w`cpnV4KlhzL#K15*C|qin@sbo8C5NTM z+H2{x_5^!EcUbPQK5Ti|dcbnPYGLIbvol0mLJr<&Jv)~$lN20(s$s$*i&^P(L|8q* z;Qolh$45jjY=Lx;2Mo9qA{OR0frr!Qz)&>e5SvQ9B`u-S{OCJ><42!8n0Ma3&zPen zrEnEx1$Fp_+O4zu?~IpcJmq1nt+}i(T%Iu&Gw~+MSW_%7896v<`sN2SrpBncxL6e& zkH2Z+YZn?uJ{TW=R284w>lME*r~n<72o|#oX~C<)Dj(~HzVEYGBYIXuggj|C>;xELk`rDlZAC~{EBls+1m~Zm zAd_e9)@;WvOV`(>({=Ufj-<&Q8HYDRmuz$-ovyEk`*f>+!r*KoI>yCRC1*}a-N=sr zL>T`<^pJ4L^|p7FghlQMS_&+&d{4eN6vz*T3PQ1DAQ?;+Bx6m1reIS+Q>;7C9qcOT zif!RH`)@6{HFm`Q7rX2B;O!wIYzv2i4slycZbFP(j`SwCK~~_+Baz!(sI;lE4NnTm**20q_Nu3<&v`SIa_bHSSO2U6u8tv?v=!%nzg?~B;5vLAB^{*{tMGUUt|$;IsZWl>b)`i<3Q-`0@zs8s zcPqeP=H1>TpBiU0#ggMv$ymUpqA7)GytfK}SGDR)-QIkh-)ghz7=>(ny?ESA*(kax zcBd;6Y(W8UfEWnic%UdC1a1uM3ebTe>=zs@1+hqZ3rc#E#6S|qlSN4(d1G={l1@VQ z{uZTgTtxhuvH&%RQa;fS-fdq$i2E#YBDdXKTPvQwPk+v4PeW$Usbn$6nySfy%vqp+ zfu*5OTsF$jO+IrndpO%#nma~#KJc7}JO7fvRhWH+b@L>=e%`kSYSM|(0Az2S)}j#2?Ch^4bn4z*%S;= zAFapN7VnsQ?ak+)zTGBbJz*8m9`LXT?en^k^_m6Cwd>h_Q@=$vTe(!*%aU!VjhZbZ z&0zlO0&{bL16Fj-ppz%KV697QScExVrg0y!Bq2%JDbF$aHHfeFA|hRR$nM$5cF6-wk(M5v*-&9 zOE&Uwt+X+yA3G#@?$b|ypSTrKc6Nf5p4*x>kDc!&qps%Q z#gC;WGwJi3tsn{JJh`x`^x4hmTKtnKBbgBApeRsXNDyEZZLHfcqqeKLvTnXHe|mM> z$VbIYB$Xm!BIw*;J9F{W3sM(c+BGk8MR%#ZIOjtX;+~ec&-|$nF&iAQS*qLB`w9*h zd|vQffn1{}3i1tsShwmW?cvbj@ZtP-@|8D%poZ|D_g3x|H=7kta)tMZTL-vzuVb&c z)2tAtm!Fyx9e=vqT#IvD^VbY!u0hca(VL=VG$mBHE3uoa^daA;KJ2?@7dyZnWM5|~ zHdP%z1|CBCe> z#_c${eSLFPdB|*=P~F%#@8ZX!W@F~Po+~F6`2B%UvLrrb-Rc+hW;H`Nz2xVT4KVr{ zaW}0%ZnRkNs9f0GY}Ts;%&E{Sxk4tmIDM;KVjj1r4bQb$d+OBFz_K-6x?Y`nf_qxpPj**?wyjW$4ZAMwznb=J1%-PnX zpHdUaqtbc8145I}jwfj*X^E63r6m-M+-_&=_95!L33=^IFLNjJF!MCC-*VXUj^%TU zj@7b$!pAFlxAtk5TAUOge^!^vstPcH0PC^&l!}Os70t8|j(DX#Jn76}YDDrz#y(Go zr<~KZ;*K)w_~2!Sv^Db`ELAmc*~T_#0J6unm&<>CTPC~1jPKVuCKtz@`K6Qb&Z4I0 z7OiM4nlWe5qmdLrlY2&5$K{oJ^kJe=C?!iqJmO}ILGRmC>nObP5?+E9jX+1v{u}+2 ze3#q<*mJp;3Y`=sf2u`FqrHZG2Es6ezY~;JyVq7^+h-$f5Z&AA6`|&x{*iR$ha+ig zWq2ft2!uer`z2yN|o+F0Fs8Sh`GcAt- z;6N;17>_e@e}ytyV0SoOVRa!0)EA&j+yP(W)_f?QPX{N+ zDvBFWouS$OXu6+GF>Gp#sEOLxlpF?uNHH}mMCg=ce<+`lG;m?D1pb0A zkSTVmHhNWTUJQR+P{t^G)Z0m_v>00gQA_59_-l$EN-}Q*{o+`WR$gtfW>(^<%rdh} zOP7=o28S-hWp2islm>a!L6wv+fvPjdh!rE-iOl>mc}Z4Rt*}mh1mjl?Ote<==EHFC|qS?XDuj!;KCZTbiGgg#@-vxC zOs`kO;{@vfY>8N>^3!AUoL&vxv#tZJIplv+X|i#N~h(RlLQUgX8ze`W(rJ9l1Q#Gut`Vg(wlHqToC`7ybPum<~eP(axRoroGlNb3}t}OF3%KZ z3bobe*ChSs$|^;>HTxsbfBz#Me?jx$j{ldm4tVc=k){xe!+kB>Jlr0*18~RSuE?b& z;4X%D?*FfKL;4Caq>rEgJqvLVPK4Vf($uGj13o8;ypnmMFAM#uMA9)4=9f_Bh?HM1 zmdn+_QEN~$yqged^ndV^cwB>c@|P$AVFJQJ;MXPfZ4Kgx%4~gOwCoY7e=Sbx=bEf6 z;T@6B1aU5plXyaaeO&JEf&JXokDkk2A7B|=9e9ZF5zm2}zKb$YZPd5)Mf7jRE?ESw z6U+vAP`+EyqIg+(k&05yQq59>VW&ZwyS0ApW7@}bEv${bMQ`{w*L#L(hWCw^7{4(s zHqSNxXqgMwkJftIG}|}!f4L3{uFIVb&WBv}uA#i0?rS_Hk_M&Gd{C^7NDeB)!qo`l z@P*=S5T2MDBYbD|Y(-WJr6&zBoKrLkts{#6}Us9Nl4e> zTXHmm3h=!WO~IwW$8$7;lK35orp0tZoue64LYO652I(52IY&c1#0-gMr1BebG_*@> zmT0-yF7Z2wR!Hx@F3}pG)x?Lu3#KTMYvN0ZmH{mzh9#N-T2B0@L@Olvv_z|bwvePm ztHpHEF3~K|29lF#f4!7mD$xe9PO@5}jS^ia(I$y*mS_vmT5_^P+ksY-b0j)XDzikS zRb!#tP#`4=t;dxvIKN}#mbAk zXXDwXnpbz`f2giCYrX3(>heN6ee1fsi?^*>xn>)LUFWx$E!xzzcI_CaYrS2)7m4?h zE?{#>*F}&zyK{BdMVEH^r-sL^dgWP_ug7KZJ;ib;SIdQdgF7NLi>&Nomp3eTx%Xi%Q7HUFWXtDHM3s*f_ ziIzire+pVBQN7aLgE|2MJ5e)$VkcU2He8c?Yh}>~MQ4isT6FZ;Gy6U`whzsc(zA75 z0M8{*zE>*1=|v zX^7YYfpLh~41r;YxE=!i5U~jYaEq^lKsQ8ee}q6gL|hAjW{CI&1nME;8VFQF#MKZe zhlmXjD29luAdn9cS3@CMPwd0%o_{O?8u>cT}6~Ceo3jtN`Ar?V=lvoVUze~^8r4p|ZUGRLK zf9Qng9_g7ad871PNp!>WLZB}smOw2YskDb!GKl3uor*}{W2g^l@FD5>sPtq6Q=jHo zpXTX4&F((UhCWSopC;L-sZ-)NP&wp3Aw9dKXHZa;YaS}s^p|V+atDFy95&8Pb`p?w?FJs7c zJ1_DK;U5O)+8{WY4ZaPL_}k#zpr_7`KSy)rARqi0nu}*a_-C|>e-h&# z;$?&wet?%r@%K>#R#p#wZ!q!5>IX*iMzQM?L2)MyWGX&njNInF4*#WR+C2tvQ z75icorTZ*qQA~PYC77W7ALn{JKg{)e{<-Uf=X=A}2Ea*IsM-raLTGgIMTbFwtZWYsvp1a_kJ6pGVZiV-L8CmYx63C`sBRD))gOXef z^{kG$JgZ_u_|E;Fm9a9w(;SED_It*+ z`aCtz(xZc(l*sI$r?ORQzBD!@3ax0DD6}$9K`iX?v?@I_pI~M)RZJO^U<#Qa!!db` zi!sR!a#pUDtK~|$e_Sq;Q*uI%iCw#-BJ@tQO)9dD-YFG%G+R>y z8i1MvF@Rz+e- zXaEX45r|6Jp>x?WPRd@E&0dym1E0%}XJ6;C`5fd&|Nr#ss{cQIWAD~aHa{T#S<(f3 zV;9^D2DbM1*akK%^m?Dz{7LTbl?E3qT+}0mom~T;Z17zT1Dp8q!001fx2c4H8p9~WMH#e7YH5L@Ngsc?YP69VKm+|lsCzpx)6g~qt zH#wJatP~RkGcqzcIhV2E5gvatF*h^{K0XR_baG{3Z3=kWyu5jw6II$ad`>N?s;reg zsjQVsRraL2(_5M}UA@xK?6Ni53IfU^vWN|+AUYz5j_cq$BQoQPIy3^>xH1aj!ZUtmw=LseVsY(hd5|^L_6h-|wU7N>!zjI_Emqy`K97KmdP$9&7?Q2ruYg z6TWXrO95SZ0sta-(aMXL|9H(0b?Epg0OZz-FIj)lt>Hia1ASly01K)XFPgvbH(y@& zcL0`Ohdxuk7+tWI^q->h=h1m|@$xn6Zn(nz06PC20N6K|tXMFA-OBkN0kHA|0O0k@ z(HX-~9|N#@9=bn#>HL4?i@tp7gO}0yCiKiNR<2&O^3Sht{0#tW7XzSsg*+y5X|u6;iX^}YQa+Ux!X~1-Uj~-{syh^C3s}mKfG^v z7(H76ji60?|Ht4bU?=#g+7oaDZO6e7JO|Fk1-y3HG+Z-$3f*r(sL7xQoQFQK99)HZ zayK{(-bCA{=&5m74`;%aa1-1Hk6>-MouJ6c!`Gr#(g=U=X0%N~?Y|7Q^j5GPVfZL` z5^b-5|3b(!P{3NTP1s4?ip#__vVZvM;oZY84*v>`2noyx{~okW1Z%)Luoc{Z+Iu^A z8oUfXKx6e+P=XX>p&PYfAN&}8g#pZpCD6CMfv>=~;kyYy{FwNd97}Fd9#)PIUp%~h z_|4&eq27OEfI`^EK?B+v(KA}WG;j{+1q%_@%QSx619pQw-~htzJ#Y+s3`#(OLDbI% z*aqjqE8x%IA$S!26T>hT3!!Hwur1gQv>n8bV(;M`9>C*xHGV#RC%yxJl(>UjNUkD( zMDEdjMQv3UC<~Q-# zA1;L(;6`{I{4v^o0(YW*>_giD_#Au_z72naw&U;w+Wv`=7=?K-0h2Hp>p|OWw9UsB zV2gjTCD?N8hiH2k+l}qR262Fo#k=tNXdA!};_uo2-;!5Hd#8KiHah&)& z@eRq6Rpd-^md>Lq==yaJ>h@C0s3)mo;33maO#|ag9ydmUUXMxw58~E!xB%W%~^lc=1dYDJkK!gc>Q2-p8#A>8nv5pG0nZcq^}{Qrk= z(^&{3TgxMF>@MVw;mCi^weQ2=cv3eAXUTp*P~&l+CyDVmj&Y2R8V`Y&X1?OPrh8JU z$tR1GO2x_SH^oV8sR&w%r6PM0?W(KtR^A#ze>~YgcA$M8&!v(wRVRcpJ~)Rs{ z!Ds;SNVRsFjWp=ZCPQHU9BPSgdSu<_wwr&pFw=m(CH!e zn<2!VmZ5*sCt8XpPnnuhv@JQScA z3Xzx;3qq#@(}oN=^0vBKNr+%hhbvD*4KVF3?RqQ=D{o96Kv zulG!7u8(=WwC-=a`|gIuCo$!f>1}JDomf;phW39)W?z5&xkYyY{^4E9?Kj+p z9K7T}OO)2-l!tC_>p^1x!THJ^*a|cTR?sQ?jV1y&(MCH#7)`j zAT)pM<$o}}A|fX>2>~rF6}-?3c%6N{y2@s+Z*c1{iF4&Ho$IsH ze#c#T%MgA(q;_7&Z(9Ff%1-6R>Ck`N zV4*BJ)~d6+P@5dxhM!x!PM6lENwdmrI$3}EZkyZ%z-mnbeE_3Slc-pIL%o%aVX+36 z+h)baP41a;-Rv>;d&g~Tn)WVt|JHZl0_B_jjmx_O{N-1C02jc}(-7Xl&v{Duv+@LL z+ZHT{t5}T#Ymq^a$K+da&g6;VxT7i8@kx^-tpCITT3W}aGE4e z?jv-9RKsQfZHRnQtRjwX#HvxjbdQA%W6PXFJ$1X>6~&Q5{2b9Cwe>lTwo=`eMGs7# z^3w~G{=~H_>rz2MY{aHVz1>16n3#Wi=RE6_d*;-7lebPZ7cx;^>M-Clf0T_1_#Bsz z&n3Oh5$Ud0?wp4su|!sIKfbmY@K5!nLoN1ji#`{!b&A515@qiHiH*-!MVLR;nRAaO zJ?hvtBcUHh7?56Skp1u;TwhHv_fUEh8d3Znr~pMky1%Oi)IE&XI0BA9! znrSD;kt#+i-pN}9D__UM(oOJ1rR^p~h6f3QBHyI6!k2DRpYt2#Blr!9g6xPx(rX07 zF&f8koe9Hf_D;&+M9~aeuPEn!F>ec&seAm zI5n!E;WxL>u+JNB)3+zPn%Y_xPW`!m6tCZyG}BZ=ZG7T_IjAjD;mz1M%&cZkWIbrc zAx8o)K@P&*NSd_g9%nxXIn~bCQP-xze<}=S-lWOGN5fyhmryKi0FrE1HMoitoy5FG z(<{76BCXM*4gRj8hQYS>T&}IP%A>xt<#O%XcM`*&U^&EL9E_Dc0LQ1;FbraUH~|5` zF+-(w&%z|&;iG$gf*#>j8|YD0Wf3C5+A=Ad+Q1%GhXN_VHy0{nE+Q9y+ppSKU_%yg z4>=D-=Rt709C-qMW&F&69|rG(&j4Nx>d1l#pGfN-2$JRpTqbO*Z4jgPhV@&yL5$iH z4&{!XENkVV7Sxya^>QN!7$uK?=#Xg9FR>yancVt10GwDIqyp%Ow4ft{*IWmn9qkyO zt`6c>v`e9X+OG2N8n~(t^hIsf+89#ExX$TtyYe7V$YUT zT>psh6Up;|3#^d6{f_G$nB=H;V8+sg^zS10zb~YICpdm*8ibcVWxvod2sg@BlM%-V z+DX!s(Sybn!v|s2exuRkb$gzN>k$4A!9Gy2%*fHH40EvsQF7APL#N@_sT9&}sADdt z)!}kG`8q^Tt7?FBRGpZA$1a4^SC)>hpWume=S1NOBiTzbrzTEH68@mBelC80RZW2P zKUa}U{7rHp@PR>jqG*GcfJ@;LJH8OR)AEhw8wWvJbPlIQbPy@pfqXXT@#+oGpvQ5* z;U2Hm>oMpdsdMO-nMtSPK~4s|&xmieKJR@Fqmc`OwC|pPKX>$hIQz{>G`d_faxjoM zw+yM$RW=^#k*cgIlI0F-WA7(eTBJ;9lo`FzKw9uR$QaSk5-i#Qb$Hn#TO1kMRSmQz znh{Eq(z5A=d=0WiINuB}g}Mh;+%PkjO4a=3hR5FdgK|B5bKc}?ZpG=}yWv%E;bT9# zZ(+k~=JUPp-TR<_^0sniw%4fqscesWhyRB6lS@GkY?q7Uz2{OG6?H`uRDtTDmQqXU z_0*O0W8PPz1~>2VxB;Gq5!U2!V|F9GPVF zEAJ^Mu3W&2KVSXAie=MofEd?w=atJ>PfMRi-Tm~Vm(HJ-EhHM2?0ixAWPFmV0-D^r z5r2*#?qk4z1UAc|bvPtVI)cG)6M*nCo5_Sj6ENb09zdjppMz_Vzi)(h?bqX^-l!jh zL74#$GF8Oy2#gqn_0NQLvhD#Lu6qvC0}C)BXe?%pK7A+qilyVo!Xt!P*ndZ>_W z$)*UTl>j=nsJJkYx96>Sr=Xb%Y^LIbU$tHE&O1AQN(YHsOB3+xPG7ZIc^uB%tc`K& z@R#`2$*$m{Vnc79ZqX+$sy_S`fO7-Yv2|$CMR5=5CK+JtmoZkh+v_RgQ!Z46w`#r>9DEz z;V*vm{DAT)^n42&$qoC*jo;)Mo0&Ro#|O_OyB@tSAH4C-wUJY z*Zs+I$rk?TXye6CoL9f`XUc1DZY}G7lQ^=GYRqc{t7ZR{`YrU&D0~UEhTcY9#}G6_ z8v#QzB!MGqkdfW0aVC)O!ay^C1oF5){68`#GULi%DWaKZ(lu2#)wJuz zxw~SW)#GaB;(f#=#1isK;xc2u>2lX)?#n%E#G8m4$<6M&#Qpew;%V|3{n3=&S?Qum#zltO0o%NwjkWY;}<3ZJH%ILnu|`kU>U&7Aab)2|?3p9K9_#Pl@baw5KU}^nI`;Cl=dJtUWy&9_CVaSi z=Zo_jH|)COw@?4{yLU+@a3)ipu z(ZRp2UVil}%OBP+?!2mJ>D8<-VPS1**O8$ESo1_qe@*;tNIbRY_J84$vh#zxl~=F4 zxijXBdwkX77X5pF2G{5`{_T}+TKy3TafSUH>SyuVe><+|D$a8pZ1I@{WkJ3Y{r z%e1#<(_LB{cA?njb<_qs2!XkB)vKe?jjJ5pgpyv0GWe5!u8@g8JI)F7hxkhte?QKd z#Gcd!iv4c11O8*Q^PL92e(S8WBcBgj)d-`lYVz*bcOoEcABsNO)j!nsElBrGBQHky zWx&01gG75Rm^~r{W5GxU#3;<(g@PM{$7T1)HjkaBk#W&LP5Q=|xm@&7zBcL2<_4j8 zFTbvmh!`q=r*l+KX-JEfInQg{Yn-a|Kud{xi*3=|sKSVdbE&jHY&3<^d;kR(J`sjf z!2tA|f@0aJRK+-Y)*#u+f-2OOIW{WDqY-r;o8Fem$!)1@d+nR|JQ9eyShkhVT-rZv z#q??+EkIk9DLh3abOpRX6m-QxG05e?-Un zEGD0SIj-l?11w>e7xUl%nZtFEX2P^im$soB8NEJXL!Z=nahIN48fc_IRe#xBlTblOKvz++`<+qwuoNLMdbe>9c+LQ z#X}7!N)5tFY*Z`dAg}6V=?v^!&&m^}P?FIVd@>O?eHXxsz@d zeoDe$k&q-sXC{jz&X|oxMLIDE^Rmr52b&>>H-%pc&L9w}Tk1^#Z? zrWIM_V3s3?Ri)$Baw5}Kb_3La5y?P*IGN@Jw+FzK6p_;I#7qbTY$i;4GBY9MjWfYn z7EM*WnlTfSFowd+JXsDhV|9EDF>1q*-sM5VNEqU@HekxT|2mlSa>eDPm#-Q>{wi$j zH;`XcSpCD-KDqqy(n9Q|j%)8c=P^0>%7iPPd}1S1u)2^)wOoeZvZ_@l-T#Y!+j>&| z$c|h7IyZ;?B~-lc8Ch^uD>b;P|MJ71kSu~-K>pklGWT2m=k7mx@Ltan4;H6gZqldQ zDAPWkq6T5B96=}8PRT`CTyC3DO<~!xJ|-FsQS*1WUvmG-jU#VUw^y>v2n}N79!b=U zm(q6}!KZoZLzNfS85DxGGLPv<4NK1G`*8mwW+K~aQ z0Z=~uC?sllqEXAGf?#ETV-m0O<_x*QZo*nN?*b1Ws-E=M`~H0BQRS=7YUQex9c`f zy6VpW=|ocMv^t1UH74N8KewhuQ-;m8w6A~pu<{;%OnxXX%#4q=ufp^by-8)q&y;uH z`s4m1a6FHcStP7X+dh7i@}BaT@|p6o+2Ro*a9n>AB}t?syii#lUAOxeuoDK4{$AtV z$h>h1X|~=jUsnt>@jfQ5BI}GtgbBV-Jfc!BH9|d>_c`JM$NPeDLE?Q$v==y>5K~^( zL=d!p#hi|cV$kVeXe^Zs1!-1DcsYdd{)|bg&`t|%RFkjr7^&pJMntgXiyK4`4WO=G5*Yd1BRxaJ9E;;8XeR$`xGf58Z>%WXDcWY*0vG_ps}VFd644rBKCeOg{k8xhqa^&jf$qPUVvPF&6T7p!~@0{I}as=sY7CPI+cxb{JY~tE+3l`iyZ`qT% z$Gc|ZJ66l6Kh(ITCHAca3+L3A^&paecybr&Pc?MP&r?1s;P5TtRtU?a8^s%9PxzmZ zKD7PTZglx(SY`)i1+NcolkWB18+14uH zYbg2)$=WP|IvlVk#6WGJ$JZX9bO~M3i7q&k5+03dDfk>A@+Vi^hZ;z{9>Op7sTj=sm*hb_WuC$~`X3SMl?BoL4n%K4nfP zG|J~|)ZAOKh*W3)APcTw9_F6BHF&AN*On_>^4qZC_&eXGeWjac7Gp`} zs!pVbUl8@3*Wznev?6)XbzG{Pw{z7mn-ZMF>zgiAeo!jiGR`1~xHDw!%EDb^l$%#K z>%|ef^}5EtRdVwp@8#a>z1zJHdG~vDXT*gY(H&m53pe8~2VtT;yB%474oM7015em9 z)q{If!2FuXsDP<-te7U%pifdQIkMj>AvNXjU1L2WIzgBZYvx(A&RF}ZN$!|C-vzA) z=hiMf_0$<-Ea}ExbUGf1kvKN@fbx@38?5^R^+iNhX36I~0+cc6{9c3GpD+{*UH)1A zi~Vmf20f!U7}7SM&#z~Hn1n4D3+(1%nia;S7iI_qc%QG2gkYV8W6(tm)RSVmA_to89SHbOn@EAM{?_S;3wi*Q( z&83t6QmHz;LJ5hc-k;*~igulJ>YoH7(e2ACT2f`o-KSO)=b!%7qj}yaYX0Ex7v!S| zI}~k$yXCgudf*)IV(-U>FU?;%KlAGhYS8QTdb|b??ImbnXOX?O>9HX2g=i1M^SI07 z)^i4H+-OL<-L|-YrbUf{Hx%d61QQR(HFSgsLg&PZV(A0|=!7%5qBTEd=uEYIsD$vAiCsW*rPr~jZKp(v z0qSgY!*{2DbgzMZh%kM?makF05g~$EG30lI-7YIj>3G!-flyQq?1xY}{nX&_7jL}) z9nks0FW-Gp`G@km7k`26fEOxn!i-h~omAdbo`>D9?tl8QGN|l5Jn+g9*oeJS&YdEA zO3c*as2DgbcfZQL;m4l#z8=7AvObuiZ4)fM*hn0I72$nSMzBSKF*{E2*E&9cI(R1LLIFL2 z%%4zy7CgNFr?WS=1R5gj52xL6`ti$~S}(=?kbn;5tM<|5nJQ|qANDR27 zm>7%3a9xFu(f`25*a#n8?lX)8DRr)Gl$4c-_J83dsZO^!g-8@hat=x=LH`Fo*%#k` z*?g<=ud4rbg#QH$?}cL^Nu&+kI5lfrX86Um`;L0};Uw zNnXTfc$dq_A5=M>){6<&qH;W)IurB`mAwYdk3f#}P&r?9NS)^^Ye2N$f^5SEwg@Wx z_oPywPa8Cw%R}hgA}Q%yi137mLaZ;(5@@9WXXl~LaJxpUhoZ{P#)y}Jt-0WTXSYwj zW_C>3)Zf&)0&CdQ|GOiP!-r#&o0X>(&uZji9KjAt?FXJ}6}(x6DlIm2ZNk@H(#}Yk znaA&WkSqPWDyDm{Y<2K=wAIy z5i{s1@do`3rY+)!`gcXDUJU_%O?4aP-s&aQSoO|y)~8+xaU11Xu8UllYo}z7izYom zgt85GoXBPwxz<-V2$#qf;Hx*WdNC6BQ4L-nhpz4S`+~kXl$tZNH2B!6)L&A=DLhrE zpA#8`S3LvRDi&i4jk%-m_H7tC?qT2UL-P{tIvE&fT?p zN^bUBySfgWr2w7GRM&iee94RrRaJ}jJ=WRrj~6cZl_lyWGR^Qa_~UKUZ@Fs4!U?(a zm)GRyUw+?Pol#zyc=*Q)=1gfUj9syK_MCM)Kj=<-;XeFm+0XJLJ1~f3$_=*5V|B0L zK5RGKjUhX-1H&$W7hqTjhA_-J0mUy7n)ewqg8em@4|ChCKnH1m+G^6-vk+h=Bke_U z@f6kr2O%w+DLP>bSue2eu z4)*o2YF@*x6BCr)fOR7Y1lwHifbk6$aV-gRZDhU`%ud~m_3FVv7s)VFOW zz=aQ~-1Q?L;AfhDFDZfBx!m*|#NrByrUKKSVQhcM?OGYyht>fKrT>fN;;ez4|6Y}3jXMk?nR zkZ*W9!XgApa=SY3jJdsQQJ`=ncqH_G@cq!Y_HP{wV`m&*yVnu7#~nJFVJPHeGhjSm zD0{|Z8)svG3=S&{`a)OvBTAIBxoE9K+76cI9b?2ON2U?}8a zt6*_n#!^nD|leJgGj}w%CzEt zsWn^>&Sa8$J(ELc*{mU7TUS>DeW0JV`K&K^C7<|6(&A&|2EiBY_ciz$<8^Xc z{X`-wh2xw@((8f|rrnZBk`j3O@qtEtXrm?pQI69-|}zA)e*_Z;j7n*ak}mi1xK zxTz6q^cI>1;UD($E6>v=J<&){Dsi5YI^rYcs+D}ycSha%9|r!r2WpWBva99E`GUrO ziSne{f7(%q6h#V!Yvv+d)K0dPL$_6ZXY&Nw5l2STf*Nt^%O-h*IXHz2F&qj8h@oq6 zxS7iu3^0CdzOO#qibW>{vNQ|h5V&ZvK3IfzHUw(9V>7P|opVw-9J*s*`OLY9Rv2T= zIHcfd1#A0fhuaJ#3F@tNHea#?uxG}9WI49rGGUTLTLa@tJHAqG8p8Td{{-%WpThVS zQY!0oC-ReaBHlGaqx?`I+E{~KZ@J#SS-erYF?VCtjn&9))0r_lRpY{)I;Y(oa0k7T zx7wADMH5n@x*#>ySaBlY2soXXgA+MPvJ)Ix!&O)7#6V3TpN|>>VVw3cW6}wK9Qc}R zL4rq=BuTs)2_ql=o+Cx zx$38QM%2|JvT|{*Hg#GqheL0mKM!D0_^tk-xVxA_@|{AXSbo*^8xE-#)WIwV;tdej zBF^#{BG=XT{XzUr-o^k&?sM{g&U0p64dp-Hv33ErwAYt#=xy))DQ`2RedFNg(=+3i zDE|d-Xqhpsv~zandGGwa&dJw?CMZ#n>H0C;2FJg*gpeehF{S-62d5l`T(ju1Wh@LD zU(6G`M!C6k^xSHr7{@p<{3DoG-f9n3d(t)ospdX2Xgu$)%87);mykkNsAZEm3c8WXMCGHb}hKxe5V=rsKw0&0Aa$WM|Le? zQ$+^Y4SGFfD42FInAMQB(*_9i4hMiT>*MvdIaXw{WW7t)`FMH`H3tl0%pR9Bcj)Vr z$F;bNEge6k9jIo$T&6K}6uFp3HOF7uZDD`O6yoHTk-QM()q+;R&O0Hmt-ir|XvVKq z;!pKtxVi*UIK2fk@134cRMnhXk1cW&pZC^X3(t3-;%3(0qTH?KHz3%jynro0hZfK& zN2wR1a6^RtC~DpyP_!8W)@-vPB}~x|SuDWIT7S!5asayjXZ5u@OQ$BmwPJm(L{NWq zNX9@XzYp$yy67~TT&=s6X=G2eZ8dli4$?TC^uazHY<=O2IlFo>ZnjGsb0z6$jve@&eK>0(oI| zp%&nvXZUaU4fsA32g;xo>9~u&lumz17!~W%VMgIz{k?_9^p6=HH9gO~!MvG!v+5(} zqpD-oCzzos%WOJbopk#{ju6OOEYLE(CR<;qPFwUq=b#;Vk;;y(H_&674XspbA;ubV ztx36heIl3Koy3xZ7?Abto|vyA(nfU1gBaY?-lm4+CrfG(Z1Hn6%BA9QHTQotR4#MY z>cg~Bv7;jku(a4>%-9woZgfKm6HSs%vPsa4C`*kcg1{Md2T?d~m)IbT#c?B$AOvO$ z*+K__HRcG8h8R!_Ge#0e<&uU~eIp7>4L70<^q{^i|)Mx>a+g0&6>l>Wq>atj+ z$P8jVm-XCQj8(O*SHNxOLA!q=vMis?>P2TDQ}yH{FL#QxDd7%^VX?8PqZyxAf9~{I z8{q~lDP?z<9B*C{KSTrsn+Hs+P)G`EH8 z*Y{yV;7%7+1;Itzpm3L{WeXT$Ys1T`Z5kNVhD~IFM>Zd7;t37Ys9d9dt|7gYg z;U>sztG#K<#vhK`O8kE-!pS;aX-8G6=U=bBtNeOSn&6wNPtDK#di(a$>~;k!V~|4_ z^x(Te1PI_UdHjv;2kArB_pR@H{$Tw)NYxooTw++tEY&Y@E^%GKtk+-RyuxLO({2x? zXWbsm?XgpO6KTq$!M+1I)~$YnfVBxB4}xf~$aaKOwu;g6e5!w~pptZ`uccTzI+DoO zg5yf@hcAFM9)Pw0D*-c)c6w{jE&)r}Bx%)TtVTF3=Qg?;ONv!0$j(%Mlot;Q*cJP; zRYIN7XbSW7tAFz39gC-BoBV~A^*et2SEzd}jqko@5?`pwR@J$jM;^NCuN|?O)^%Oi zfAe3zf=N)Ba~^-fhsb_p2b;i$^76amkHz1Nzn=bM`ea%MmS?ZZ-k7~F`*`Y!?6axo zvcF6n&A!_BQR>6&dyQs1H8Ft+wBZ;rRqr?4Y7K_nQv?3s?R-sjRgQ2IkyuSmaN}fc z6Jg6(VQoG`HI0qMcteE9*^`;uwFe)>cjSXw#+aD8t+Z+sNuX zjb)XWGTG`1@pN6fK>~$1+SR9OCD@o~I*V*tbuB=S#>he!)mbUmv^#M}MNlosR%*av zYAvX(zOpJtTYH&pj*Gs$(H5GXnHyn69R@Kh+-YzbbT|HlYD|oux9h)3&s;X|#QtRm}j2o<1+cr2hI8U3uv6yDk z7t!;qhlnG#cZtuLKUnBywM5>7GkT2C<2rYN#UXz_4%hkp3j%iPcHK{Pn6A}i2zZe5 z8(@E>hkBL0A^byDg+3IlR0G%wCxO4oOX~E*NOtBZLazU#zJ8Taxl+Z1 zmjzqv*e z&f~AYaK*!KjkoE~vq=rQK1VvC;LHGW{4bEe(+q)l&q;Q?=J;DWAfBZTMOrR>=E z;K6)Pn=$K-cdq(cEY4qbp=j?vpH}`MUpAv1MT!u|pqXru>#^9q^Dh5&Cq6BI8EUFs z{StFwC~VOOa`)Z;{O0k|&W7>{dlx!_RckN!b6<7$6)$5wh_A>y#y>;0xe7cj zyE%^zVOe9G|{eybZg(qg79Z{CdIa)X}FGJ~{uXVhx{H*B~QV875bOM(}I7zbiT? z-0!^J`LOfL=(kaSiU><#iLVbg@V$=Ss*A%*tL|~?`ADQ-b3_EELx{+cb8LLI!&zPB zta4OWR|ydwdF)cKEJ79#M8N7meFB&KjSVrqvl_jR2ZSmtRuh;P+Y~z(!(&Yisw^5D z)w#y7;n+cB98`g7DyTlo@#82qVc+dLq`4}i^)6cd0k^??oLxW96E!}OcUI>a_cX~ z9Ju+`X-d0b_3IhsjeKyt!uzS3bMP<6Nf_q$_&gSpIsME@U)bn2;(kBXv#0*AgX<~I zk3pl^U^l{lyOoU4axXmp&KHak!GD>Z$(TKrKk8GI}I>%FyM%o=`w#EeZzOetdQ7kBJw8T{5Uv2(KBQqwusT{AWma$Bg$lZs;!as5@gK(T|>XPdl za;>+2$ZyM7w~Vel(?!^0<8!2j%9Na@gZoeR_j%MAjFE$Ka!yOnsmp+dDpkDY0HPwZ z<(Sl(g?)XMT95BZA>vK|sxOEj|4AE{V^-?>mqxDeleu^-<8+$4V^5JJw&2!@7dMBU z6B;g9u(LkYGF@q&-QW~2X_dwL!g)&F1+@-;epz#Wrmj~haFjhT8zU!V2~zNwI`rl( zb341F)}H9tA53^anLjR1bAG!19JqUac6!R&pnz#JaE>!}Y=)kDn=WjxFMT|*338SC z3H*=v&p{s4f@kDjFE@$9?leDaMusuOvo)c7j&I>fvBl4aBPtp5;i$jK9}WvB_Nh^S zhp@~_OEx3=?F3HcZI;AXcg#ZNYxuCfma3J+TrJ`Aqmg^2-WJQ|aGX$uxxP-dj;v-L z)pUbaVqH|_*QzGQ&kO+BN0g0YEbigr9tkvL2D_8J z$MpdFxa(^@=>iVI?VhQh!IDk1C-225Tb?#}ogH2Si9&^^eU=W30UFe3!N*m90ySG< z=DLY_-@x9cg*$Iqx&GeUMeHtUS5AL$L^%ez_u&1t=YIN>a`df7 zo`f|;l|zi?Bkuu5#G*rTZ#`3g&)@02N8BYo6gwXKI!5b7GB!cPACAJ#D2&i-yUCwF z7R|Q%Yd#S?VXNje_>2ZUPG;F`BA*Fajd~Ib`slF&<)g3#2}8cdOm;|Gnxrm>NMt?8 zLRO_wdCllZnxv@NUX4K3Ge0sj`eO`g1RIUx6dorfNJY_(h+z`YiL?ZND*>z;y{^)h zJ&a~W;CBtiD7nhN9)$en3Zdp-+;z!4JC;niLV5Z8yqGuGZ5NP-NV2VF?giV9{AGV* zLauLHp;*_n|K{M})90oYru4Y3V?2IvVhTmCA=ts_wzP#CzdHJw@~qj}s2o_7rG-k~ z0O9)uia}W9y}l#Q)Tg+AR6`2io_;KQAbT|XTkMPMH)(288aC2myPy9g;?o2ar$v*; z?ZACHca={}@{)tb3D)b;=c_0W#g0uH_^??Q6LXyGgFel9HFfOBz>G>e&3V=G1KNWA z?*VJg!;y$$WPwR4N7-!%NK48j0YeV$7^^M_?GAIAm4Gp;M~K^h^+9dPXeHfOacraY zXUVU&AQx#<1az=Gn>-@DXEHGum?l&?JedtQZ@FRPxwXQ$6%5jz_P{xczjt<5e#Vjw zZ$tRYWvrcxH^7Idq^Ev$*Pdf|hus)&BOpN-PRVs#jP~=eX(8Ou8La;A-@T<4=0Lz8 zu=gOG6JV=6@lX(dHbh`30adM+K<2AmHD87>(SrP#+u2Ach^L~_2r|YAN5CH56oBDC zRR9ZY53n&eg^blE4st1bELsi+_U%RlOXiODY3AsVHVHiG_|c(NeU%>)_%2yt-&xg# zqh5c7ewCk&s8m>}Dz3rFGv^h>YCUf&taI1YQMkNT&db$*qBO}RTr{;$vGa8GRQSq* znIyqGl8oJ>XCLfc*qlu?cC9KZ&9mAuzRb17!$a8XNS3QX4a8;M4(!l{B}@g=V&f9i zdc%6-R--;aSNW^oLFx~EW)R-C7ab!@%*&QY(&P8OBm4Y37ec*P-$SQP^}Q5tGXXt~ zT5Qr2WGG*MW24A?(okdJ;9t054&%D$)*4V1gR$DkApD2Sg4jyS1D1mp+|mVVp}LH& zum4?7-|->j`ypM}f2ePjCpTLDGGe#OD=9wf)0R?{7lNTnoaf|c+R`w!E~c`yA`#|U zl~f5j2*cJ$5KwV42zWLWJhReNt$E3h6uoH4h3}Mqyo}@uL-6c+7r2yn$GW!k%&uu! zdF7f1*4JH4+gP243OOQUFYlVV?Yj5&+)}*TY_yRa6yccF%`4AoOHLN$w(HMbd23v6 zQ2sQ(e&MX9^SZ~axwrq$xYdXsK-~1AaT!DGM6T6rImuX|FM>I+LNKC}4ZMCFHP&K~ z^}Hc}DhGKG!Ez3tN4BONC@ekt5%(JRQS%`WTRL~_`Thw zgA-$&5z!UwYFbiQgq@G;NWH;P72sEJ zT4XIOQegYD0OmwVwF~MmdQ>V_tlFBJ{gCq|Z#2BDBhXziSqQ zTIm5CtW7b7xA&RcqDzAQ>QqAcl-4tPGp)QOCcRbo&XCR#VFUisDfkB8ZI4+QJQN}d z7niE+X15jls5flWg~~Mo$>9^^%?O_+NXyqtlvGu47JLQ1DWYR2+Duz$2UF#VBqD1a zTO3U6|J3#@&`n%dzBe>qHp!usWKYR%&TgADY?3w&fpnXmZBx=T#g_NJ zCmBrQJ)1qdtI>P+&b{~j?wfhPd2@$>$t4$IB5Z`?OUtdnV3pY)%$M?irE+UlHdLur z3-efl@k`C6`Q;$t_b)XU7TOE_!Sd345XiDi*`g)GSyGA8H5pQMv5A*0hT1GGlWhu` zFrwp<$*9HK+r@*)S(I(eM~Cb$Hf-V!vk20-26=?ZWavq_MATAiJG*E%0Z zsQE)6vE#MXqT%#dniTLQ@)TR*C64FQnBOKoWduzcllWywQSm?^h<~^T&f;nr~_@oqo@X(qGzrOm_r=d0Fr~YH&vj>)xFZhm!Bs@c^$|yQm09nu zGM9tcLH;Ozntz>t=W>Txx_lXN`YsrQY2Szg=EEl(*aW?lkO0~mt5uLlDEbd z^0;tQczOPDtPQsY5^HR^vddhQ(VY7|Te@79_*F%j_jfN<``Xw?C7fB1xjfK(Tc9n+ zYGx&gQ;R=IY+4jxB#B`x=FCPVlHXZ)-Cv;3OcNx@P*{(bV#hK?kjNgzM3ntGwDIJ< z$|s_IU&_OOf+37en^H9Z z&D7-ihR@zOq@4n7Pv0w5 z|B{~7XJry?ja;)_rZ1g-Xc~DPMptyybUwB5qhT_++j|-&pG%FXE6JlOqEJw0vdy11 zH6IbMC5Ve#6k`~UGHrn}}|#v!_+U&EPanu|q$ z+VHNAzdKS4ia30K;>vyXjy(TE-^XP`HTIljo3_l@G9)cglENL$(TdJ5s-)!R3l>JS$Z67?oRfE9LvFeYX^L1E5_7VDU8#8sowzvZ zGhf;<6s7ik2C#!ebg42DX6Yln;Y?k;Z+_HXs(XC~gMUK>zwQ_;fG#Bz*& z(p;%jol|!uP!dLi4m-Bdv27>av2ELSa%0->OIRo~w3 zEt}2nt~2{`zPf5v%Gc`lc|Q7oJ71P4T6S1=>MK5^Zk#_${mu#sq;M&lYxN~2M4Oel7N_y!e--=f#`pc!gUD;epKaIpEn_9O6@IVSC)&FK?MxQ!lzOSOM|N@ znt?*BH%DyaF_*&zY3`dQ?&hn|_>h(CZX1C*yyWB)1=K;!#X+9M@7rOBC3&e50heVC ztFdgG;+b#2*!g%Al+>RFWRmw3vuO<@X{*ImOi?RMZs0}iCTI|=QIgwqg=NmWiS3QK zgv1!1i-m=7EjQZ6Z1?fV5EG_Ze!rmI6@uUtf|R>wVlNx?o{BNdK$l1 z#)x)+dI%G23hyx0D`DW*WM^)Ky>{P+F?_{9tcG}%=|{GWF0 zm42B9^^eB2-+A?H?_)H4xOW9z9nZ4kA;F3lu4>hAV zo)-22hev-cj|Pl&v@=OxbE-oR{fvsIX96OK0T#oeGLj>)A8CifI-DY9SY4wCdwNP6 z8%JY}>HL8df#xjPGx;fl_02_E4N9<`Y04W@jZ7J3*|_Wp0GW#w!xME1QGoy zk0xvr*I0Edr{xenT1<=bIr~a^l~VCyJCl&dQ3R5q{g3kf(TtA|UVl4FWdmN7OPJKM zX~S4HRi%z3BSiDN45Ncq{EX>Ds{6gBheEKQHDps}q=Z{>Cs3)ligu+kdemi~EXFzj zc;1_b%ycddj4Jw-1f#L$TJIG4nPA#=X-p4}srz2=g-i+RNtV>*QNMLr84LMxr6z}#H|r# z=(q&y)g0%EvGe=lFX3CQ=7VVCMiHGw3SMdg4;DQ-y0TPo#DEq&`cWw*VybD{OGam$ z3iWLYT5T3N^j{>*y{cDJC5N0>x%k>TPQ*4gl_oaEPx$f}VIK+ccD7yqH;Rn-n!WP@pFQKPE}akFmhn>gts zlF3h(XVsc4Oj@{9rl`mx#v05X=g;&%McipO#u%H^yj*R0b+U1-sJ!2!vz--V!0qTA zYNM+WP7x{K%}Q0?ZL-fz5(PH}LRjb{Kj+d`9Movx52F1Ul;O!(J?er$<*u>Z@ zM#cyF8qqkH%n9zo6!vyxw%Ha=-_g`L!T|y+H!UHD8z!(h(SeFU z{86r`VPB~N2_p>DQg^R^Ow!fDeQv+McQB_=wI6RjR~)2upuTbef|$tmRq{|~eO=Hi zeP#gozb+&@B)_*#sUFZo%MS3ku=pCXUTH3jpjJOGHJ-q2@!fP6s@o<e$UxZDmq z6FNrwkN#AVE#JQa&|gLDqsS4@;-)OU>ht7eQ{AX9E6cXnNA@GmgsMt2s6d>3bdI5 z1A%;Zgg%XweGgK)WpM}Gq7CTkqF3XsA94#y?QbMx$|~p&YARIGnd~-EG*h2lnIWP3 z;NzX$Y@osRG$P{xIo)6Rbea2-Mim9pq0m(LL5+<7z`?D&9AIa63W>_rO*?&IhvP3R zJmO$S2vtX62?zfwfu2BTot59dq!++WO)nY+=Eq9Nem+hSD;DfqVDgu|F{Hpy5t={| zX*yq^WGXX~-VO^iS4@pc2xR;6uvZ5pJLTtvscE~zfoQ_o*CV)2X1muirPIESNL-na zaPBYw^Py7iSDyd&7yr?)2XooWZXEL_rW(=H)PRG@)e6TWqA$L_w<-5R8;;^WpU+@b ztVqi8$h%*Ar4`Y33)^->G0!$sA0$7aEg`AkzTU#tLN4`^;EH@teY06JYZB*R1Ak6J z#_g*)B zMnyHQhvo*GRoemSQ)c{##UXGTQ~Xeb54S__2gD}M^>t@=U?{*JqY$(a<56(H9-{V7 zDdC6iVlaDwE1KBF9HYKYi#>FW8OGRGk}>a#!R^`Zd*2QOg>Lta$Hb5li{Uxw<+3us zbXOY`bN|?>N*n)0g@wyah2Py5IIq=D%o-!S?Vx-ajpOpY=^u^Xy2r-hTL~_P`^OtU zBb?|F{XDZYc9dUKIN%>0ef4{_HI4Y{A3n^u=z=W7*`m-LHX7{dS79*EBX(~NXp~Sa z(;%`nPt;a3%MZ&r{@!QM?!Y;aMd1p-B4Uzn-UE0MAtjzBgqajkunOz&kiDp)j`U1` z{W6G|kY};5C3RNWgS2U*kvcA4mP4Wfv6M^Ba%l4R-f>f3NpiclsbcO^{(IE2rIn+O7TH! z){5UBp(qZWzdsDZ-Sxsb6@R@+5k0^xLS7OoerGBa$$b2k@h+0#T_pEylG$7*ueL(H zve~zkpiLO`mcSc%&Sm!$v%JQ{ZRI58WjUr{5DJ%AeF_6F%@ zUUty7og79a;t<=O-tpgr^MB3Ov)tP1Wl^k7Ic6_AvazZ7=f*)vt+_aCR9~j+jQ2Ut!sz2<`b?!1B zZSFk6_Db+mk1&^_?=%SEZwpuNNnIaE5%vcZ0=<>_h`zht@;-j^nFtUR1Mx5OcC~DY za#9F`%pt)KUSgO$h@=%6M{$XUJACes*aZ3U*v3Kie(RR=Q|uHYj7BC>O+_j&s_{uh z-k+4NnkV9_WNNE@musT}0u!RfR7hXav{R%zSp_38%k_x7^4&Kr))tTxeiBRIJx;XL zV5-8j{6x;zhK8l@qe6pbCzi5W&S*SiR9m!a($t->*lS-hjh6c)KPS`i!La+~cXFDV zL}_anHbV-90JqF+ZxLVfCuYBkiv&Ie(#c13DQlHh*KGWG`l%rrK#ur&FlV z-w75pXO0OyN!S%Q9JnbMku8M(Ly%V*!ZFc(H=!!e5RrUHISgXZ@CQ=egm{r5wNrXT zm*k-Ar9;c0Z0nXX;5VwESLrB$j9-{veA-|RRFoN&J$ z4SqQmu)={+^cN*nZ5zKQTt1O@WY?>08bc*qjwD`=_+NbrybWb`r)`d*@A&RQzv9@G z6p|rSJ3Z{h+fJOuEJZl(``eZ9D_xvxcNJ&$R==FK-~(4r4Z&m591Vx!HCDx-y@E+x z!SF54)P48GSqX5pkadIv480szwx{S?{yy$TH?rSklht>T)=!aE>CiRQYC(HpN>l{@ zT@s#ON8kFh@XN&NOk+{CtPPTeSZsl zet&;I{;MAUKL5aYfB!zA-kd!UkZ}aKAOCx7Ux|j+@}`R3%>vHB$(rh54o8uSK7fvt zdKZCC`;Wv8Jb_1TVU9#^PkGA}wZd zd80t4c&yw~;=Wen&4e-DTw+Q4(%0##YXZo{=kA{Fo#&Cv^-gUVxSYoRF`YJ%^YRWspzSefZJXKR~H55CtA$TZ}Z_! zkA{+&H=n6P?x17kes{LBI5%PzJcgiy-A=32ZD&tO5^Mkxak0bRBTLCo{I);PQ=u1S z0=hh(#ec?g&A(9dt=xKFyDy6McsA4lB@ew01j9X=&I6Z!IJUey;qw1L=_kbYab?aK zu)=&oX8SGE|1HQ`v~NV&ia_WEx~4rcNd^-N-Ruj!q?(iHM(PbU+-ja(qS`m5%Mv44 z+*U*doCkEDaMWaR|)20oYPH_k*DCz_(bePq;T(LUx$ZgFSCpZ&l- zGc8~Rf(3fFPLb;OxL!Oeg=XY44ZOMdLsR-*tsGEn1aWVPSmCGibAq~;%mW@?VSPOi#=SX9Y#5He*0aBPL~0@YBAmg8GdGbO;Iiba$|2kO$LsMk%N^o(l0V! zd`7;=dW8#M(_Ad2Nx8eMKep?n;cZ4(#2RZb&8NQUFV1m{xckf-S63|^oC#;nT|_k%)s-N~{fNpX@_YSA4Fn%BHnEQ| z1N5xmWTS@ZvV}H4(<*JM)&p}<6jxnx0d1Br1y7M;S#pUncW|HN0(2pPDZytmZD409 zui26lc{Ju6d0S+&rMIqZELPFL3P+wDk2m1!b&ZmQjOk@9lPOPJ!KbDVB?)^ ziY50KpMDAN$t;b=q&e0C@rclrFJ>gVsvuD)nqS&VBR|1OSyTBNvB6JHDe{)0IfjFYifG}>zJ(=iUBsov(? zQdA7Sw5-Q)^T;R!(P|7HM_ngbX3%J^cIKjPqY~?^k($V22Ptyf0#A5_$HEA=*Rej4 z2tAL8sELF=mv3!#HJNPZ^@)~sv^K~S%>0iG$TKn^pe)R(JA{E<8pQ%+msV%4hi=JX z5~Hqv;q%&m2LPCV-=_*57!|{|To}>azUeEM*yqsY-yKGm>+@p}RzU3P33S2fbja9# zXdbzF&dPLNJ~-*x50$Gk5!Fj7=IE45_ww{LYkJX%5{+r;(4n`B9XZ+uGR>FBGwsuQ zPZ|Q?IS+B#I44e=Up+hNQ?;y5JA!VZJ-gIobCa7ukDpJ9;YZn25i3e31}BUPM}7oI zPaCsYZhB(0FiG-_!(UL=M7Hrfj7C(!SuP|+qm|>17<>1{Uz(Gl_NJR19KC3;e`yaj zM8(>4X89oS;Rez$jHJdIM+a0!^CaGkx8wpQ%{sW-yg61ru~)K>+M|PJe}4b10Yg08 zxYxPeN@j>clfAPuQctJ0+fayIFTK#+8bq2iOBel&}FFe~c~kJD}p%jhuMe*SE*TmSwc66yf} z$P?fUU+{_cP@K-R|I3f>g{^VK;=GQFSm{}P{lbPHiOQ|*TrjPo*2APFG*SzJls3n9 z*C-~w9h_W>wG#bPnD1v=Io^j2pMN+W#~7#ddwc}3qN6`Y%0|QSgq~D{!t;-&aksFg zv513mU`XDD6IY1Z2o?YKpR8lT6u!-ry-RnNoZewJpv6kcS#nkbru^~wROv+{)@<7o|^Ao%61ekwDS zZlgY!pw0S1b)NDx2E0p;IV?TIQ6K7))Xf6{-mcUfb}l(^VO)RF;z4~LUqNd74BMAh z((CSRFf>CO{SB+Zp%S6UucR2Xe-GmABz-zQUfo z7f+ryqe?1{L{@e!TbIeRJ(W$n`}oUKCiHEVRDlF}lMGE5;QwV^e4cR*soh(F(9oPf z1o}$`Io~Q?)$cfiL)-N^a-n*PyqOL`Z;nW*%-g?mp|zfuXI1aLYU~nFj%R)}A)H~q z1`;}UUEj3k!|b^>0{`eFMoOQ%F8>|rvOqw{arqYUiuf4?VNtnSlFZyyh@r{K{CrC1C>)YRx zb!~Dp^-u8R4qpeuIz$8bm&-YXl$A>wMgL&W#x^vsmNLUrOXs{Py$pC&lc~%_K9-&| zooGKr-CujZ`B<3>eAcRrDc;k%U5(jl-S4X?;R=)8S?36i$kLPG(ObEB=1f9ZH*j1n z7^lQ%`(qP9@fSOt6`Fj>y|^M<{H?!IAGADLa6ov6Sv?wqMFj~!;%?R8Anrr1#lAfW ztYWMPM}=w*5LRg&Fd)ro@&Vhm2E7msaMM zV^7K&OIyT9^e_huqp%@jLE>$Av^O;Twhor8NAujCJZ^w6Ng0L^DWV)y*8shF0|OK@ zYE?$|GcM{;t{eYOMjdxDI+v)_cZm|7;x^A%h}6R~(5UjP6{SeLILu_P_e|MS=Ax6n z3wz<1?lqicfDIN6L6s7e8W*c5c_; zOlQTTRQ#axu$+ma@XRxX7#{+Hb3-8g(u>LRPA2^+=ypj|^N@KAx?R0!=anTZC z1bm-pY&?@A8ls29ZL>Oze_KSvZ+ zk2x%om}B^IS8%D?qu4ALC9!wSXu4C%@)Y$ufLwr6Bl8d++)w`n8uh_ai)v%BoU@=E zR&`-6gch9@(aTrZ=r8Ea;|2=%enOpS$e@Wia9oM!P*O%$n+ z2gU$X!5U4dOF7;8ItM=^jH6ZT+YKGDn?{jcJq?-DS#YD;(J`Vc%b3v|s3s+UZOA?N zjuPGOg~@8CWzycv<{=y?Om#d#x`1ni)0kA9t2SeEOpxP}TWv`Hg!|dpAic4;u`1)9 zXx;M9Vg2(a!KIXERpce^l9Qj_Yv;E6DYh6Ou+GonUH#er5KLEUOvhT`wgzhKhey7- z25!j|{Yf+xg{~1tfOb^^hi8Gy8IjR&__UEq+x7WWB*y=;I~w2f5v)Px>+-a-4=^0) znK8WXuy7}@?oW0vZq?xh&9#Bp2@(q9r%)myU^@!ng%VGgTQAfV*{;4@i^f3UO40=k zQ?%II=4#*c(+fDr$UYsy`(VgJ7PZ&&c?heea?)UkjNK)9;?{yPtXP`p_T3C9H2txu z=jUR=56{{`4gX7VZODv+QCQ57VzVDAOb3j_hszR^xXA+epsALJ75_ zvQal4HFa%HdBY#U8-aT?$i0bibzlyG<73{Kpr|0pXjW=+ zIXv6+Yt$|lFn@(w+ab|ukumuH>_+|#5;_Hv)z>4ng{r8a*R*c2xzA56K+yqUMw{~Q z^2E%>KP``|5r@obsP)2k^nc_aD#5NB#ZqKGb!aEI9_>L`QKP~x#0~ZmslSnIZ{%R= zwRe6Db3(gl%EdlGl4`h=K8w`mx=;oZOws+ZmC7T*AKnY<7$~@z9o><^Ycfv%%f&^{ zDa( z5j_2Pa%@taP5Vj`CHu55AWP(|d37(PTZ{rxFkg!zQvaIDDDbbrYA(&5grZjAjHLWZ zRR|qoQ_p>Z3Mw+3-*cQW@02oIoOpy|5i=_V!8dVLXbf;^-v9$}?ecgBth@LosP9EX zzM6UGN5j{$W*risHb7!*JejAbO@O#vPiy+cf#dzV@-HcJ_W?a(T7_eqbO3N#OCK2lhy^U z!09Rj`%?>EhFJj7ZHqUuUt==_Jr?s$qOl~J{xd}jYmRmP%WF2@ytj92V=^Xla{@Xp z6Vnpmo*!&C7(w!lL?>qi64Ov~51aD23pI6=a%Z6Z&u0LH$esa@#XH+Dm^5&kWp+z{ zLr|8;3=^F)^)cF_6wMpzjtOtf7Mk8Mjo6(qZ=B`P4ISdyFurVUGq+s%@8YiA3+=-b zXiPY#xI6KHaB*x{0sE0G3mUbljP7wwpOv%`0rwlTkuU!e!5MFRHL7kfBNQ-C38NIrgfMD|dYNu-PWQ_tz#=_ym zb|E5EwL*>11*;aD){tQknM|mrNkhsUdDTvt!i4#tCz@ugVFpYlnDO4-D~b+g z-#BhW43q=+zOus19T`6Hn)e?ozoHZ>XYv8Ye?!M&pk{o2AgDlW9qa?hW9>$nV&)aM zY{_gznmhFwV!84pY3c~56!C5EJjg_7qx#|yRU>f=srgDz-r%^v96pm&=!yB|R^vF` zeM%eMb|yLp%Y^boX-B2o*|q$ZP!&tf!7|k{)5hk;er9K8!Nz4~z=q-mUM5(kFb^1y zumC{rbF*%vX~J#PjnbZSaqryTIVm*IH9GzX+);J5cG}Nx^)C&)CN}^wmyqdaqj3zr|vw#b&ksFLV+?GmmR#?e)olNWYpC!(XR=LilxH=9EME zl5dCR5{Pg9vc7pFE|K}D`c3t_b!jQT(e-pkg#f+`-BQ%XX?^`=Y;!B{ z@mbNg$f+oT=uCHueTaR_c7q^!X-}jw$9Xjigd;oJSzF_JLNp+N zv`9lln^4JQZjChNaP*wVFKTBi`CIa%?4{%hbb^5f@R4>o&9z|HZgc#wQAQ<_`3>C6 z?};24ByecqTtT0nOPY^H=J)wY{6L2*fu~T9eWro}|KgZ+M_MId;Su^(vPJ1;8HXQr zU;S@E*W>&xp1%iGHKr(&O_`BgAdgTEZcnzw?E4wxCE zmpJ{${U?fWW{EQ6%9l`@{8NqRDl5^*EkcV4%LqhjRjMdOgc zlfRv0AsBURmYOPCzRjj-urPF3lh=i3&Uu>hvY>w~Y18L3ZcHU@SSwaA8X`y95Z0FP zIj~|VkUZSW_>h=Li_2}ERaVJ50(ANU$zUbdO2#0x6?Z-_&HUepkP+NneC=jRqP)jR zAw+nx4gQJdW#wrGPw3{D<>ZS-q!{rhOiAGKlq#M&vK5?&w_{cqolZT(BeN(o8ZNdQ zy+^ylzxL}jVXG_pM}zPzQyX(2Y9Q^?*@2(%Q<1T*a}|8NH)k40M;>-P0QMs!us%l0 zX2xz>xGH%{Eb7zNKEH})e(kbL>Tw&Y{qc}BMC%ehA!oJ%&4-31ACfnDY?027YAJ`M zlU<`*AQ?=|JMAA&;H$g`=k8pnc4MM~bVi;bh*`3$9FewcRj(0Vu`(cxOip#;&6%x13BUNPw1K7ztGv1FtOXf-(EuL#%aAjFaJ$t zCc6wbvo3@s%$DkSj$1ZMi!6qSbYBjdbGy1uIGAcBml13`3`gUd0W`pxa;SCZm@y7H z4V3zEo5cBeY_>w*E|;V0c%xVIRlPV3!Uulg+&CGKQOE+5wmozWj;%25r80XXG?I}K z=|>8xD(cx62kP>-RW$rT%5>(2dD_Sg+{?#0)%Vd=^w57(-lYI<$Smm2*Oy z=>#BrJIPa+1t!{G09GulEf4w`m!1twPJGD~vYt<-?^O|1pJg-X)k2a1mK)w$728A_&d($W5=h+o2ZZ-_ znf;rgp75IRnvkvKNhEKcoX8EJP3}|Jy*2@zo&gn$!-gs^-~*40eNO?4!P*XA3=Cbl zCJgDG{~k@BE}H$QzG~4pB4QSwdU5ZwqMT)@!@`H(P-ek0o4{cF;1QVdTO$E{N{2wQ zKKyscZ`bKYH#~RywknHL{W47G}FVte#{xE z2IlIDipUE?fJD)0M%X$N;+Hqmt`O%QN{jsNnw00QY3I==6ah|LxZJ=sj{krgZaK-G zB(OpOsI2tv9FHyFW6#Ym{NYBAVa2Kt#(XEdc!A-mNKo1}SKHFDflE%;CvqAtYU5W( z=0^g-oJac)8Ka^6%JrU}cya5-Ex$ebJ3Wre5)zpaz~mWynQ_YY0P7iS{pW!K=w*>T zal^>8U2olw1@A;X2^r=1cPXT6nXyntJy~2Yr5uDSX)5fa@dO9z?RLQoUY!3Rqz7-Uz3w2gvH?Sy zGu8AriBl#l8V`UCNDgohkWAZzlq_^)y)Q6mAVAHK&yY7t=|E|tF;lTM9;9&; zyv`?;^ib{)a-|U$&|3f0N?A|MSoz~dib$Fa$Wn=6_AOGul`|6`{k5ZFZ!g%1WcBRc zLaMjEa1|-l-mkf2cTkA5cYH${WSNy*kNU3{}$evR&r$$g#vz zlFPgHJ=*BAN?($-s&yTk0k-4WaNOUD#fk{TFUb_?o?IkQSb#_3(CVdi11KDFI(X zCI>Sg7vEZYZ#$8Xm1ohT{{!k7g`(WMZaqOYuYA`|$w>n;F#+p3{HT1z2!0@%Ixx4% z!|P+hn*Sx(tmjiduxTEkiC+z;#!#iM*)TA5o}48rME05_O>S(g@OQ;tMboTZC;^`@ zTZgRtJdtS2kSDLMI!O4IeqBTbQ(}sf9gQrshLX!yKeUO`{1o4e@46moTkCk&NHe6+57_K z&ZsIWZbka|LmVg&2^Hov*_C9#!rP=b0B-z`WhVGryR%7AX4z4E_k2OwVf{dB&h%v_ zUv#!cH4BaK5+*=~+G8iLufvA%cXzwggYrWU!!3bIKh}3wPWM&!K02>1|6gK!+^3T- zl(VZk;Q2D?S8W@8V%DuEGggGkM%(aj>qV_p3TiRq-@aW8Xpg!`^4sQv6xY2<9|mQq zHyRrAGcOVW6@`8@_`Bn=mX5lFD+VN+efEYA6uFHlp#u`W5{r# zU3@e@zj~YUSUCDO6;JQ9gVz@^tY~u4GgZ1-n;Wxaf<@>Hm2z&$`CIHu1FstKq@Ab( zF;bAunjiuENe;z*!Q6>AW6xJ`F8$7J*9VYP>mvZW6o`&`Jj^NUOek>BR1_FWg^zYN zWTXUTW^_1G(dw^9!jfx?pXyse9eRC?j%|-IRL|S}SW=8xdY@-E`w~i&_s=PQW2KJ& zPVA%a>&G^9L0Ut=r(v`Q<`^95&JBPDa}n|?yc>W?j`xih_8w-%K1AGUB9Y}Ai2CR? zo(cmQ$l5RKKaah|2G(4TOicq&{+^UIC!rTY=S8AOS0EWT#@*TP#qqR=`NP)!IAaEV zf33lk>Tuu#Zwzh^(EpM{lG5@g6;P!I$L}dJJk;SW47iT9bE>W^k*v5l)K`a}!C8La z{4D|mNo~$tCwca|vFQ-m3pqAJVAzqNZ4+ICa*!QO9ElEr`SNxXi;91g;8>A^wrku% zDMfPwmn(AK+8l_Y`i%VN%UWV6*TXfSk)&dLBI&^4XQ9drcoLQ{e#E;OV;z;@DGuhB`k+ zFTt;af)Bh+=2741qqvQ`=A2y~@*nVDkh`1=p)h)Fs_Qc==|z$0YJTzSiRgs2L!V1+ zp(w%VkapN?zqd=jA`lW*?P)iY*LDxhTo|nxI7~SB{6dyG-Dbp~JpDOaB%{iqhI;?* znq=148re>K`tufG;5Gw)4V@1k7p64bgA=Hw5a{WOG#-!S)x$FhoM6oQ0`dK_;WZWts+t673WV^jeiTm9BiNmv5E#U?k174IgJja zi4w`m!s==|g@dU=zZ8D0VYgO)#9;VfhnL2FqvxWaHs=fAcDLR^2NlLBdDbO-yHp(W z*8d$&THun8WpE~!*h)h8H`>{fPN!0Jh7*=&AfQ6KJE{7p_BD!C2OKd)MT*n*qRGbHeF zaI>$~+WAh%>GM@H)A@GJ(xK;@j6>lXDKNAkn3Lwv@p9+^Kb-3w;9z$`p~#7U%${#9 zM(vkbatyhPfHrKvm)NXvQDJL@J&_G~GE* zfzij@Z~0nty4GW06lLmiUP~zAqj@Dl)&m6ydE>thF(5?RA$v!jka{O()j&C|oLMOd zquv3ItMpz4+|uejJbu~D<0akHz^neS+8=W-t}m!BCSHpxQ))~LRoC34%K;wK&>n0Z z@HNmmZ94Hz&~mifJ!}?rBh~<|hcdxp-Cak0r>{jrsd;MkW{BIVi(IHOD;klxb)+00 zc60M6RF(U#_I4P{(p9Kyq|LU-K1 zY_vCCv8$245A+a9T!jv%0#P@Fw<{iEcBcZZcB(D6s_My0lHD#jQ+C=&a!cynPjl2C7pI9{JA_09Be{&y2VNRf0@n;!;m(*cyk%3G)Z$fBdzhkG}?z78k$EI3t zOaE_HO%0S3nV*}DN2BBD>0?Xs9~rybraUf8GeyU@i+7W%?vMs@0jfOOz36#1(sv&UhRS3zjJ_>L}F64Ie1qwzJ)^>UyZV_`LvQA^YY2i z?%QiS?p14iR%#wHp3KanbQQJ=TYn3Gn=&(trTO;c=Hz0NO&r_ov+CgvUN@C=Ok-8k zWfOBg$E6K?_^+r=a+Ib~9qUEFJacVvunU`%gh!F}oN#YX*Xf{hvT1dleGeQXn=rx^E}x@g2ob|iIMJ8P^E z*+_pl91vzkzYpF5&7KqFq0>xGNFgyBX69v_gom86Z{cUBO(rYL)1Pt9IqTjXU3`2_ zY#5PRGpUSWL;qEMc21o-d4`Sxpc&7E`$3704!6)SAXHi0zZVv&7*1%H?0tN1C&7QO z3N+GO!GVY9!Xv;26O-dLe%R9|T93f~^-D_D$sZEx-(IN_>ZRdydGpj%s-bXd{y5Df z{Yc$X*RriIpO%HR9Al=e+CD19s>B0yh6mWVgqE!zrNE!Xc8+{-PA>x)MhT9rr_s z8%uKWLiY1)bqCvM%!CK<_O@0BIyKWRyyRX8k?igJE%5z1vxACv>N&$GM)v-=tX&>G zfb1W6D^w!{1+uX=z1vqRmt8NPhrI&FpA$)`9cA+4q@Vr~nq1OxcY{pT{02M~`U0KW zPPck$Sn#t5ry%^nr4_!VT7t~zv2~j;OvQ3=YCEsO);`HFs#6fINj}evg^z&h9g)FTHJv3ciL21*+?Nxf1ud0!F0KAHF*p z6+f6QajD1A)cgx{X26C*ZoHL~E-XKtzZ~tRVPH3!KtqnoI!M9;)J|(WMo_iyX`OMP9T}fhi`6!Nic6I*~5tShhPsU~Q-ia7VBPz1RMi zVfXuBYae(W;9;5w*MR1&wP!@5FJx$&z-B`g;u$F^w{Cf2MO{Ja#X;8+7wRSXH^5HC z01rjD6QjFqI7BoN_`&~?BFavCSmMk{nbpIfYvU#lj(tGb$SxCynE=KP^*Pqcc5uG- z$RW)TPL7WQyo%$%ty>8NoRU+iId}Z~gMoaoE9c%@RC=<#F+5pa0M2->JcHNAkzGs; zr4hl3>6JEqTD%NVWR0?arqffA{a%i(g#R=~V*9{$o4vvKK8qgeRKIwF+M3CFh%LYSvhkjl%D59^NJ za3tOFKsOelrAdLkkWm*e^B%z55>x_*4Ecdk8-%yK1JIolQ=vL1)o#JywyBuMLUWmC zJ7dSh6q1Kos*%NjIO|?d{MEW06>ifgA4G(w#*+r3)3+mYN+6lW^ddom;}Hv06Ejc> zXy+I`f0#H?Co{HDDYT{zln9nE0WvyT2MY}wDd4EMXNVS(vCeMLwWlKvMTd!oWZ~Jz zP(u1@dMZ*!(|M!c@40z897J=V2%KJT&!l&pfr|&0&t?PKDr{4#5^WM~ns_I_w3`FB zkl#FvV~_IA$!-h+n z@r}aJ0X@>te=o`B2Yd7_Jz~reB<{^6SVu4xqgMw6#>PPeGX@}YH9nqdar#2Vcq)MS zK$$iE!bXcC7vaM`&(0fg1?#~JdLvqdMH5xvT!CMi&AZU13=hzEiF>!oqX>wy|0@Kx zsMepZ*2{IU8#SR2a&dNfc}eUL-maMOn<_k+*LhI~C$1Ks+Qt*C-$K}Z)KD~th+$p3 z70C$DAiuG4vjw>OY#oP;z4bW&Omta@7IY^8b9^ za`V&NVpja9*}+5Y5NnQcS(ktg7wQ&gG;ao3T=08J5cGm+Waco5z5xA9=|z3BX{Q^; z4^Mpv1&2bR4$c?~V`oe)t5Maf$vM(I*DM0~3V#N8hFaB#9o1$3EQcp#sIz_`_n2y?sH~>7x>3QH3^Rm`6isHh@>P`7a~=0tT7p zC9#)oHN-DV}eD!sO#_oijA&wV$f-WP0vs31OUK%C|S@>y;HmjV9Li9AkUraouuXUCD{1|6f}mtjxC0Ib1v$=mP; zFeO2e=8!*wJ0KeE^lRLJ3}@t*z>m|<3U&C~xec6OhR`7N0}VL&;YLFD)FIma^)dQ% zqGuJss!48b#8C4>?eE>uXcYh!pe`s?u8(onRso86O8d-^?`FEon>B z8_d?OUcHrBkJoIyz}4>~p4E(u5&?ls&61ri$0_2E#lYAt9M`smW@$20IUm31u2XrN zm+2$iYnqoSvEFLyI1dF#zFB3^3c?@{ZcsJeM*d%Nqu>wtj8=RP2F8FPw1>xE1zz3Y z+8UsRn!RN1%OD)U;R1UIk|X@JU0bk4f?W?#&mX<9>1@n6^= z*;^6a*+Ys2yO73F%y+;Z8{~6Bo{&os!LP1D*~k9F+dD^B)-`*h9XshH9Xr{vZFOwh zw#^-MYVW2blb4U7s)Pr$)%!dT4L~^;-csjxD`@o#x`fLSke+ags0-X@K zkZFcrVmU7a-s9qT*3KXMvY-qjvd)A9i&d*VUyi@_p=QW!G}ACTIuoDE9A7TC8ZK=C zyWd8h+g`TOod~~QgWT1JZh|@1BX&-+;F`-2Ub2I5ioRVE%Kz|}GgLl8o#MoNN7wB1 zL*i#ju;$2_t>};QRmY;=`UCUC-g{z9IyMh7@xxxXvA~G3hc%#nu(wrdtD}4iIQHWU zlIop0aGu>Cu{zk{7g?U?RJYDv?jJ+zzv_$h%Bt`w$a>{kXBR>Y$cd~N!vh2!Eaf?6 z^cPh}Y^;HyYiD@IUbk;oB1W8whFFduwqOED zCVqjJ-+Y-HynEIKdkP?<8~vPGjk{w@{wSLt=0HiE0%FhXIOsunvM-05r6!5dYL$e) zOQ6R_-bU2*(92>~ZM|L=%2N@sGoV;oh<@nFG0RZ;yPr5vz2RRY+9ZLP!qxT)_b&{wp8yAY&&Wi6uuc9}c* zPrjT(VFV6r8r8wYX~CWON`d61s1$;N(?9s91&EdtgTOrGxr4l2 z&~;^r;0h~s;0~DdZ-XElgEf%dl&~i>cA#Gi-10<14pR6rjLtHR#83*p21PAAjEcGf zC{j{^CCUlp6H6Y)vHuK36hQDhJgx9CY;6|ZPCd1_CFrvJGcxm{y!%0G-km@U;wfVn zU?K4F#su#tfRzgmFFOjBo}2ggVP_cTLI}yM%*j#Y8Dxi}cLu=jU5%I+dgs+WNy?on zo({9O->^1qLe0DKc9jReyIVm-p{}f-$^m`Z@cK$tFfgp2AKprM19VRqHNC@6;s|to zWp&5{LRYB=8cygJ64o%?9$*3wE+zaK4IB8SCuo^q(`1TG{+P|VRk74&`rkyW7F z5?$oOP9L~um813=v)&Cbs*eW0XGK}$!nBfC;3lit~VFK+d$` zt0LvX=49hGbVFofkis6k3DToF`Dytn$QocPn{YYu^X5K zuA}~ij~-ke+K=@OJp$QYBT1FnKhopkmdv#NvcdY(vMxQ*GI7saHBk{xO4fG!x;FxxGq(q1Nq&T{ z@(CHIxM9e+U7S;VPb#|%ljDYJ*AH02Uqf|aV=LaoB^1R7#NP9zn73wYQWhrGa)(39 zT$y58#%{M=I@w?AQD>+jMk$IvHQVSC&k)Vpi-LFE)iKhnE-T{!h93n8Q0x?`z7qTU z(I$o>U~-gPr;#GB9hhYauas12;nay`JF{=}u8HcN%1Wn%fk9K`meiKJ;KO%ql*l0`tDS5(faG*&_ za}~rZ#^(PEVRWApb!ZJ`gKN#Hj29KaM)n)wT2;wO0t>E#TvYK~a)jf62=1G_94LV) zOsCvPDP~%^Fv0OYfkxMSw~?J``g|p@PIYatsg!(_Y?n8fwVC)H;WF0tKIkTZ%x%W)|-SsK&jC=uuS?)a|yW{Fus53 za?%6k2?8`h97*6FS$cTU2#@bXG{lW{L4D_uXbsPi&v&3a41yhYO!T75#Iwk1eWkcs z?t17K=QZw(TMe6%f26%QOwddLp{aPTsI-5rYv={_dXLa2rF=Ed6@Ee|JSzd z&?9FQu-P9Zkc>S}&)0OK=56N=KRmV-%A2#x%!OZQ=7vr{rDHl z?Y8R?^i}Pb5SGI|K5)@tz)tng)i2<*j4Uyd?XmBuVG1Js$;uQcgVme2ZxL3P9u9hw zw@?1bs@F@GwOxfKZ3AaWqTZW62v*i7oQZBe3qMyH+y!16ks|$p1_zuKE8K#* z{RE@by$aF@@68Vt@&14$LW$zv~C}2Rh82 zXSQ8(+dvjK!I{?eNms(mkWc`Eh? zlIqm3#?purpU_n`ePsy0YTc%Yg|jB~^$TFEXC$8>XvMKm+JW*?Z1(7bN;5vRHEH$J zs8{9m0v;Ngdid_-5S`GX>9EzlmMLrLhQZ2SW=-Uq^YgMljo+Op#Bu8mX7=J0HLWR) zPQ2)e>UG}yR{8jTG&-#;MW^1{WqVnTr(brymru(vo%rMPU&6Vg=sL~Iq{!(w?pgue z_xJqUUJsz^f`J0&PhG+-3w9;8*qclh*B|Ydo)mP(^`m8qXdp&pz9P2rtR(GT@9T0J zxNKUYJAp8r>tpZ-4ElS+MV?pvjpmez?K#%*3zBf2O9;wU-zrd~q!6D%L6hT*T`~ia z>F-pW+GETh6Hy$F_fw5&$mm)eFN~*mQJ4^z@p0PDV0x{W zP~p^cTR8xLw>ujCr^uZyyxRk@HSa+(a4j*&qVby@MnCV#urhZR?rS&jvh7Ztocd-k z4svAjN2M;(R<3k>zpr091PdSzaW9yF!2onv0g{2-|BuiV6PWMcb`1KZ zs{Qyi1t|wW>z;xhM7|RsOv+dSxaABsvs2w6PGBi0(bBS56pwP=V*b`ej<*Rti zMUULz$9c&QWsI$Tn;t1%u4NWz7FIYbX=|-tRYcX*5SU;B%ybeV-@RH|<(@nOl4x z4kMxmquFBA61V4~>(s3X9r;EWIYtKU0^v;+hpO4XLmj9o=&MA=xgBH|?=b!7I~74t z3E*Uy*nu0$NZ}#qL8s3xs#Fyr?uC9VWkN^!160?+Yr=+?(0i{hpGPLb+_zn~h(VHp zqOKi0MKI-Gfc?o()W>T`aRy^ns?@QCxpNK>@pUgcUS#FBpLE%uQ zAmaNfhrpetdLYr0=#@qJuvWIebh2P4?p^P?{6YY^C%gQn&eUU#GL--j0did#|)j8Cej9jC}0`oCGQ(JoH$W^_Utfjc> zNH8vGA>j?<=(aaC5IM|ap-1PBel1`cbjot^Pc2**t$n{hX5enEe34*op;Nj)?+W*% zvtxE#WbB_?!W6(>BG{FX0(yA&Z$oC~9biiD)vj*|vm!^xFHKlV4&z8|Ms?z#pLG-R z$5aGA0-bd%Dgv(5^b{uwH8=syJMm)TFC0~q;0^QM^qprYb}~JuYs)LrNlW`-%Iv=# z>YuE1eCiCJ!cL@R3U}U7lUGihSxq~BHVu;cMi!U-sdYY0S6OzhXfpHpOK>N~Lj>f< z4=V47H#$S=x?@ySR(pDz3((x7NI6A&yuVWwnLp;VGe zs<7NbQXqssnejFczOj};6p0VMyk@+qFc`4}&2>#3Oso~r*5EJWagIXtr=A+$Ou{|B zQ~F(4S7%1EC~D%YH90+PXux{v-B(y2DdUfLPe228`0wsJ3%Iww`06`b_z^4u1uV4w zaAooDvb?0Wv4EUP)p$QoYxH2%;M1-s*sv8{aMvPZon65>Egz<hu}=fm{%*LMwac)(m7|e2bEC z_PR6TjXh?J_e963Cy64QrJ|Fla zuAVw<8XNA~1Fm|rZy19=MPI)dHEJH5&Ums7*Z)R%_BlvUexAnSEu{gm&k8I^DGMu zbZbxbf3m(_rlHL#-Tdg$AFGA-E*l7?>^P4F%qwGpDmxFrdYr5(z1ybJ;%@7UdYvBYl``m`T=0CndI#aie3@r1048a3D=$lSUHYBdI_)P&PYHcwB?-f0EtX4fs9CV5`N zSgReOnRE;j`c_aXl~rh73k6Oi+1DMMch()eSu zHy(x&8pyNbbKS0a9I=6TzYE5!Ucc*sCN>L9#+m;yx?t)2BdFU@W(rQ)MbdUk-cck< zHH5K#tx%2H9D$n6yb<`DZS+o3l(sLuYq4B@24eZ0=bq-=BpSq6C`}xE3Ov?9MB7g< z%1F_>hGmApupHb>V{_G<8_o`%R#O3@tx1DAye@EsyqOEp{Ep=v#!7Mc#}Ad_X{8@7 zBM{)Clu1u`c(^OZ8Q}_b4Anw&T$+CORO6?AV22HS3};SaeOEC^sdImE^1@r$4|n)t zwz4LvD2Fz3JV>Ox!JuOUvGX@JvU;Tz-!$2Rgm=3V@bv<^SeUET$VLSSRE`n~7?L%Q zEPZynYXpfJv%B6m0VvTM?$e$xYgWAU!@S{o;@{CGNgK6^{tVxUO?0|mEIP_px0T|e)3_#9O0xOVE77E|5}-jq$i=X@dcO#2{Qh7Uy?&p~D)qj(>Gcfz^&K(ENNt%`w)K{LVw1y5n{$!3YDg!h z-RNRAi&8i95lgS|lBW9$X~7Oj8-oL%2SxJ-7j~D#Usx}akUWz7?Eb2VV$?p=^Y{Wd2qt`;PZG0c-Qq_aocaFjdmROdn+9+sdcuRTX88{@(=M(D$GB zvxctza50y&g-Wjt%pMSXbB5^w4`+tykXm*Iwc`SxyaQ~*uRb|T$wIGjKXH_9f%4AZr}O1?6nepZLuaL(kT8k@okePk0mYg&$e0_9i_-?c^12CnPZnn8&qDZnHM zuS|u*p}PQy0C57`5JjJfonK>U+kp8 zK=r+|36FHe$1YWJMeXJ1!ny5Sw%oC)nyFZMyMeW~WDCxS<``@xwFEsSJO32BVl9`w z1hiN2MngM>$q6-&)|)0n#I&p{d?uCJ_6dta7oqGZJ}Ivr4`ddd9_kL)v=>hdTSf9# z-P(L-wX<(_a;Yf>Hc+~fb2W>qj|7`=z0yFpV z#y6KH|LQr`uS^nRx!n}=Epl*HzTP=DW(+!NZp_agA#oNOof$cV!zsH-`YyWjp_3Qi zSUJDLl}5K2I@uRfhi8_&1iAB%%DwFESy{$AkdwOa<2PV%2% zAMQa7tME{X-FZxr7YDA+E|(|119>^S!H`zr6YRD(OV)=Muc~|Kd+9k`e8drFNJ6IY z3dHz^Zm`n8Uc3{kAHc+4d*!T#q?K?$c4hen>yX|;H|6;OBT!eJM~{DPZ_Wi*WV#+r zQeK|Z(rht2Tt^U{xnno#Q|VUEVdHG-&_>xN>TJqu6;`ktfYcjWq4gSzz)vrgX3p5L zOT1-3g0dNUv3Y*0M1GT6rbb3yRG~$NHT+7aLY=Z_gs(?}xt(A2Y!w36N24#!lx@j! zF|zdTorW)-i+YR+uZudlRcRmBNli3Y#k`krd7tJZU^;c*Gal)Nf) zQT6v{lPl6I5!3$LtHT~MV6WMpMZ2sZxtzb;2*UvRo`V(=OXCOY#UA4g4^{bZta9^L zNss=8Z&Sm4TSDr|r&Vdi$@~3YvGf%9J-*#EMzZuqY@0(CvxRlST&1>@YT*vWJ_Z|) zer|yg_k1}qp0q{3MD{x1Ml;9Gg2uwQ@0O=doVk-%d$bzqWM5U(fIO-6&7qk&S=H}5 zTe4+;fv664fT1tw=$ogHzXg&vo8Ramq@3N%D;`jWRfk(Mb~ zq@o`2U9L=LZ+Yk}+!&ZsFYfuS@_3w(+ylJi(Qw#4{#dJRKX7D_8*U7oWUnRulA}G( zpj&0RhS{ByFO%bThVl0U(^iOt&DJXZod5xD@wH`Yry}nzHn3Brm7~qkqHDai$>C!BeK#p`v5M z{;Hi40R6W87k=em!Y`b;FI2jUAE?b;sOZW>pzL2zbC)QTDZH%cOkXng|BwZ-qQ@eC z9ex4Sallig2#BH6gRy;~(jkAKVk0qrfzr{7S~xm66Ed=KGN&LHAm9O6*;xN8V%|W8 zj?=1WUGU{QwSBX!32kn%SC?#|qKTBln~hxsAiA+lo@US#Pvb_?k?rHL*o%Q4CV-HZ z!a-J%DI`!(PEL;h5?%m>(Q%fIuD?m5H7pF9*zU5_q0(H$Is>B;y4Jve_Gt;4)QULD z&Ii>AXwH+M@f9GYFPtQd%>W&P>$MUvmmD!H8rY&Hp4X*7=XALCPHcvCIzLzovb=kOyYoYuD{?} zT}*C;rg=)K#p1|WVrL{zV8g+f5~wM8;$?f^FKuI!=G3HU*@7U5bP!z18nG;?K~kdd z1?KLifT!<@t$o-iPe!!bpQJI>YJ6G2eeM?+O<9iy&`e^%)i{qx3+3Y1+{c#9#f3)z znyF5*PSQMO`2k}E<}G76~|xDvM#rMO}dPtICJS}9eSd7ng!td;pKQ?Dt% z6lQPC;hbBZjaqeQ2Fi+?u`3S|5W4@H)Z~iG)3PyN->xwlN5)g;OqChHHClAk(BC^T z-x`%yFBoh|wT$ATnGltlQRqTl`#TJx3^Y`M)Vh{c?ypvn*nK*M^D};h`SU>|5KTdu z+cGS^&g!W#`CUONB7>TQmd4f8+4HXkpT)FREua}u)qpm^+?=XewS4YWI;n|@Tyswm zr-Gtz;jeAyz0J^Y4-*=ZDFPieK-Ojs@gQ1=0)BW1iwbV+XTxuSv*%gV;=l6LRhc9<~;ivA{L{-~p)%bMfdb^n!@K7yffh>t_)7R^C%COL+arj=PZK02@FOiNlEs)M=^^|G8 z%JDI6^+oc&u`SVKF6peeCEjElEr8n8-C%!a;-y5j#cWc|E9Q_KU828)E6vc%E5CG& zdEap|kvWwe=Y%Pjkb-6$AA3}X!E(wGA5PNJ!bzU1*-KEurXBqUP-846Ua3v8a!yT8 zPsk@~mu^pju7DNe?ZLyr$E%8;9xJ-lWDCW`L1yp73J)!c%sr152ET90OH=Nu1)i{Q zMI~ixxG`Uci@i`v%u1&S2Xd?_J2S9TTB=I6xqSBAQhEK!x!q&^+v7Zgk+uR03R6;q8O^ORbOx680v%3Dc4*=2Fd)*^uIey$)vzELChYQB&GQN&)U4be&-0Q7Ou z%ap2$hQUQ(S9@$z^xGA|r-Q5=pb06|iJ7Z%ZRb&<4j9M-24e)4QmQY;{}@!Fv6iZB zK;OInF*^E;z-nDvq+UUZZWTUo{UgFR9L|Auz|qUcR0)XIe7Z!(R*F%hUeB^HTs@nl zh^L}+qqI&}*rp>{3dt1(#bpEIT(nIwtC`bYDSv4A9}<`L#1mJ_D7%!Irt_ILxYO0b zF?IxJ;P`vui+|N?s`4Xq>FVUs{96g`o@)fIbJs)1@n#vc+-ni?5WJnmXIpzy<^MLM!|t8SB;4YGP`q->WB>y$bvW zXuT?`DzCq+enndSf?uBO%+#t{EzoY3NVTfrh=ww|ouF;9rRA`oXIGE>y(wNS5f;Qem&9)#ov31>TZ-9EL$$zNj|zD zxrbT>v|R|zpqx!O=YIj*Z&(cMNTs$Oj?Oqb%gLJ_<4HBc6Up4XMy`(Z#j`CG? z2qK&__el=l8`YT`Y-=6!aHx&uLq#s=fMo8Bq0{=dsXsJPGaDQf9l3{PvtrE}P->M) z7FGoibhc@$;4fSE*4l;twqj522E@$*My&z_QGa;4%(>K#E`?V`rLEAqwG$)EN3Jxj zTT2va?BK|mt{)|TWi|J#rxogJV>ierwJg>cNvFLANZl=uuF4=&W%JB`FVxK82j(J! zN0o4!NFV*VyxtC`?UOm}yHBUxw@98zoC~M+Kh;0w18RMjwOP-$>doz;*n$uFy@=O@ zP7M1SB4nJK$%2FrbQAtu>_W+Y#Es>Q5`Zar++lHJJWu)X1~ zXnUH!ZG!$o=+A-VM`OD5mypnisYln~8gTw`{Iw4~ikyr}L#{4clcCSq>lm;PJ__?M z#zakq0cW3M@IL$~Rx&egU^ZUq_^KgbXGQ?rQJ~Ay>wlWcWKv_&fB#^-DHq<%UqsK;bCysLw08E#7vBbKk{p9jv#hoI`!#=iy)muYS*yTE~gv*v@o>-5k0=x#;w1GVD%ReHLK zOAV0s?>eysjXM`L<)r8#r)q0HD<=o)oQ1RjID3A?la^uXrl8yiQ$<)>1H6~&6 zkU-Yg>r?FZa`jwxZTRy;5{W;{=jHNwV0i!B@-TZo$Mt%9y%@;WxzSbTJ25%=5L0d1 z41D|)tB!HOS}bo?MOC?IxF)c9jj3iRIV3PObK6wE?Ta61e>_wZYnJ?xos~W23hc<> z{d9NX;;+xqJWtrwGb>JUJ*khmq({=C?p*7X_bR2nu&V91eq6rx8h0Rn+Jg+PD4TFd zt=z~+p{XD&21j?3uuDrI;c)Ut+cZC7CYu%#h_P7lU`OBm| zqiyM2&zfaqU**SFuOoKb<`>-Bc+14bx_mn68qT9}>M@2_4qZcHZ1GO$9LU+%zwk}W z+aF$l=S-lMN{=|)GtQrB^rO&Ev5mGn-Ik0GhU-ZwomHN^y4)LvZ=WxW2bwC-Km!xH z&xuc$25g9Q&ZOAB?=#WwG_oUa!E6f^(KhO-4(q-bzerx`a;WQ9J74xj$4-Oua2rQ^ zt9~jacR$IuG1$I8nwR!Gy0j`1Jw2T!b=Fh0HNM`y?VYTfJnq)~e0hH!*q`5M-;EeN z-XmVtkZV|M{HW+sPye3 zA(`GbUsk2XIy1I-JrAm;#_lgvvDUM@{oUYIo=)v<^?G=4E@G{lwOk)9Uw+L_J8C$c z-EB_c(#k}Aw2o@Y*NWhFMJ@LhEd+I(!|UMZ&rH%=oiAKJk%ooJ$KvYb1RAd)3X$2+ z8|M<#E>>s1J-vCI?}l9awbG;3px44;)xy5ZA77LPOE#yR<{D!EvNXX&CEjlm9SPAb z$l`gso}R3(jNm}0`1s==zUx18u8l$QDsKvq=YduR{>YA+?R}0hyJc^yNH1(_)gGKc zGFn8!)8L{td>sAgU4I8V2j1_~?^=+tN9wjm>a-(Bq%SZ6l3KYR8hdUe)FXW{yBMH1 zPXx#Snrmf-)Wi>G1G6Fg?=uFMg8}=Wh{$lhfuYEM=T6Jxen|y}Rz{ot?7v|!G@iIe zsX#jX=u3ILa8=}#G<)LynQYEu9QxbmtqGse0t#6RYxEwBtRYJ50tj(B6i6?bhq46VSN&H zXHl1Adc~#p5{i!14R~g2_}tOo<;MZ9jSFr&|7j5?)%Uj&PLu!Co8B5JtJC*wOOH!$ zvlAOZ%e-5CY3GTq@20XM!PSj@kSNW!#Gss{2F{638G(nGt3j^l?lSZzrDo-*D$kC+ zI=(itp;0vjRNdx7@y!q=K^R-avG`2xJ5ydmgOouY$>e~a8nCrmH2NoBL$hliZuNr7 zUExY;=5KkL?xgt}g`K{e9VDsO#PbYCK=VT1^k8{PXUMCnfta@4&uoIT?zfBSMX1}y z`cdx32|2kzGBmv^ua?i7*}{pzzWU9H_En(WVTu|?w(TY_=jXgF7H>C4s?ONt#)hwZ zo)c&H%Uu%Z6>z)n_{@jB*_GMk)2uyj?(G!+`*o}L@pvP%q^1|BFpb<&jm$KmM%$0L zjbC<{xul`IWT5;~HNE3!3x?Xh`!>AH9rG-w%Lb|PAC|#eymbQ;Ux0I=>ZYp)N~2;~ zh|?7X670`4@|qSd);4W1dDQi~iX^ygH&bah8L2mx^}uQm1;;!d3sSs7?@-zE#-d2X z&!B1=gh1eT>(t|Xpwgu zTO<5HFYsB!$5W#9d#PvEGg?7`u(-}(B|tZ>SY!upU_woaXSDs~7hb7mqEs5|gF&Lt zkc3`J>5yk|En2C!lO9~_E9@t+0&>Y2ENYR166$^NArb*@N14)7-}CEkeJxLv9-Z85 zB9D#6?OYazako@M^}e>)IvavRT!BYy!B^l=4RCx@exrg6pU$baB%KPaxNo|!ng^F+ zdB&^~kdn%-!R)fzr~68xw;&=#)t-;penFdi2d{N{CaCo=#-%vZxO{wBU;doF8wZfMOCb+i|MT4H6*+wvUvLqh z2ef{R>I z4ECJGr+cPP>DiVeXIP%!D}qmU40Q0Iwe8L=#j>8%Cdp_DZ*;z6=v`(4JAO~n4IbRkZ^l4XsC$g~^0?c*I;?+fK3iHz1XkDU zM@1n{kM(uB<1Y?Q2HRh5-EX{KP7RZhF#*FkblF{-sOL#H=O%q;>@$~aa2JUEnBa4| zHNNS+-MzmD4n<^TeR^_zP7W=r!}opa!Jmuukba&`qmewjg{-OnXpXW${!6sT=|dK) zI&a%u^Ko%D{v0N%kAd8B`_0ka(W=5HdzIY?y}S2hvkr!(_HjBQ#p=G>ePKrO)7J3Q zXm#e)WzuNXcTl(7N|T4qzVMQv_EI6L zEenk0L?Gj+hpeMoEZfb9k7nzWU%v(0@BdwdD&VJGV!C>bPlLs+5dzqW7VbZ}FPa*UxF~>+|v9=Ei>M z-3+=0+`TwRI)7MOx3O6BRC#~Asb&72{^=XCBKa-=e14;qX|AOVeC0ph^h)Wkg2yL!c8uxI zh2QYXuo~2_cEx(*<*lN(s*7m!#J4lx-53<@2vobOVv_zluXJSuOP^?++O|G|L*#_(Ni+Z;F0$N@Kch!B1dh69;=+Zeu=k^`;SfI?~}_)($= zxC@^VR?h%$wu=jIR&n~|XH0ibbOLP>N-@>#gWuhm;>kzX6Vw7t;=oAPOQ9V;YR_5K zlhgvMX2de6Pcvx1Q`Ot}+b^sIad8(?`j4@6uku=Gx;?XPFN^e0WraV9x@;W+vpx4k zkF<%pw_J5L&B7%2iS=3-=kJJid-ekZAm2qV=vqWj3sOW4=A#h|eovr!c-=hWE;5bK zGn!Z}Xp8}=>b670AgVix+_nWzAgepN)irL`5Lbua{m6Hnvc*Y}g-NIrNb7zncdTlC zR5l~HCaS&{yL;yYNPnlvho*&0%tsb!38rVXux#P) z*kUBS6La5-z7`wA`Ja`svc{%8pjwk&uv~YXOpek^R-LB@m$iKD^ljV{IJ`!);oqt- z0KOwvqxzX004&*eNi1J>%|qTj;7m-XYOvj{^GC4u3(N5r794GtlIY~;0`2Ckq9@Pt z&ijbDS6PU!@ezj!d`4b^9AG!eAs|GSFF{=H38LH4O=)|AZ`0D!zR_Vp5-Y{)XeIL@ zp#&-y3Q<%JH!l)q)%i8IA^Ycf(8kE`xPp)u250O>K`gj@XIntFppD`nFudBumMmMu zMv!`Csw&du(OC+L)~tR}CH{$O_uiU!E++T3B!qG|VVH|Rw#&~G{AE|%wMX!_%Bl2tz66&vb^$Ce>~XiAy>o;43TBEPiGrsYi9>XL3n`1Arj$dh@d& z;TltTqgtBC%Nais^)EjaP{})Kf_w*XgMX$}lm}c8B2Va0C<;8yTwQU#%i(+N{-bMB zR^_c?}#BWrv$CQ5&sm5O&nO)i#7LTnAq$?i=PpNlA+KGE07 zGTFFGn|Ij8dd^4Q0yj^Sxy@7a$M|uMsF@^OmCOv9m_9xAUoVW!vzR`KgvmJp5Ob{~ zZ$N72t_bhyx)!>63D<-{VT3yjB>~Qi_ z)4)MJ%ySjdOSyoYUK6uXjHdCe*Tp4^C6RWjkf(@KC7LXPWona^=5+b%;$vmx%O~p6It!0G%l2n zavjf!x~~y82E0aYyyI=1ve|A{2^cc#M{c%*@LytcQ%VT7jl2WoYK1g>ujHG7qFx$E znNPUwH8^@(QYO#K;=BvggO<4$3dx&o$8W69Dknhpr3jxV^i9gqZ2FT2$9?;#7qAEF z^7d+cDm6s*(l6AJw}1(1@JG0b`oPL~qfHHJ=OMun>>p)LNJUQRo(!Hl-*L+-n0Z{J z=ruR;A4CaQauhTU=@)2a8ak>g7(ck;$^*pa&>Qj)&W{B2Orn4lL-AIA}BE&xmGDYy_w@XG^@Y&;8qp54B?Y-zMH4k?;mROK;QOgKCFHFeqwF?j0pZ zSUhKW!ia7wY6p)xKveasjA*(6b>s|Km7c`$q8KqF957?7>?`Y<-c)1Kds?zfQ62EA zh7f)iR6H)8J`CfwDi-Tg+4jq{XlUqVE!Z?o={}tQbjMMRAZ$;MbzL8BpsP zv^2W2%ZFBm)rCr(7%UH$#2r?A#gf&F%~Ni@KyX}Zi6s?1>*S7qdIh>bt<^*C{2I}aV$=$PbB&^_+7!#& zR><@9AX{P5lW2Klk$_3$3cDp**Hlg0tQ}R>QjiB;fe*cN=kBO>xp(1iS9Fq>ZUh7r zT4w?=kFm$%pM;%jMu}agl&%z7Gi2)@Sm#0|+zKz*bZ4qf<8Cga7!8RZd!$`|@J&{) ze>TSz+t2ig17{+9DBkkL4Ct*ZCYRI~XfWxw<;U zg(X!dy}ZFY4IJi(-yeCf$dw&Ts3<^GY`RQ5;`z;^x$KKsmH-1_9C?^U%#;R%DK!D# z!(_?gDsr>Ftngu!;51hWGcCvZ6S+8{6c9G@FhrLrO`=h4z4;+ZeuL{0ScmixAzPFH zplsi^${ZtHeylN8d#; zX-+(C6=qCfF(J4pN+<-ZmZWSuYWvq6sBK$D4q`?Qa72n}G+Qx({9~VDhi%x~Rfk#q zYt2gJMzBRYqHDj>6&B>Mi*{8L_a_BXR}jbgX~qUKe+Ran49#e zbqClwf)}qLn|5V7f|~#3q1*`wwuHQY#azSi4Ip|4|EC-B4&r^n-qfdm*w*q6xddK= zJ?x6T{mQIHgZ+e3KQ(@Sv)1~2FV)dRN=1QH~EzU+SjeV*kuBX9V--U99Z2hEOjquj{-dV3}O z{Ia>{l&_eP?4U4;PqM#2C?XDs;vJU6!?zo2AmoIwM;1YFy#r$Ku6AUP2L}Ph)=%wU zaV?lB`iXUi-sR0f+Ufg>^nLp+plUl*+ELS(fwh<+*Da91MMF@s9WLz{90r&KMhGPb z65BuHEfBK|c>mGaw}3G-!qq#FALP8v=(OP6G7voKgIo;!TniCVk9@5KN7@pk84z;E zjM2wR-c_jRF;WjxamOvc2G8nOa>t(3N5~q`%On3E<8JZ z?hWOKnHbM*$9JvFW6g=&ft7kd@0`}xHRM_k$<3`QG#!|oNGIC71aaY*jQS2FlE2=B=HV8+D4=g|$pq9C0}@=RzK#UhEy-MDthF#UR=+aSl1 zeE&ue=dQrti+Bcj{OT%W-57yG@+bf9f{pl%uuA!vliw^+8@WfA(o4;@+)?1}!80P2 zhAYiUQlptA&gSYJ=)wZO<|M?|g@}Fgm@*e3ed2};$;KQm91|p1f}P*3B;HE3YS60V zWBR|r1MK>&{^JJwZ{7WCMHTe=MdPREQm&YWF;A$Mq<46LaPB6x8+Gc38ddL8sWE-m zfCTx6RKgPrj!eLBPsj=4xYF_7R`*12Vp+qB>y~8UrbRAkVwhanfpLIR$t)w7TkNQ6 zR&ef~#@IFHJqQhH=^slTe{w7!`BV0K;O&r}IV^vSrfJiZtg3N0ddvxK=_ z5flxaD2i1eaYkEE>ym`A4(}lOQz8%OVVv_8bNqAUslYNhhWu!{OP>U~VvGPVkyqy7 zN?bcqO8qOb#BLCuxiH*{d~}3PB$`NO2n0Z?^l^tT-egWhUAitq)3X(Kj8xB`S^`^s zVV>%M`)fO5MN!r-BEa;V(wDm+jpOD0@lqQG5Z>$hK04Z;M*tj}FAK%@%WE*#Ar;4c z`CcKXzc}KGrOk1V@zwy35T+CvnrD_o5_7CpTsOyPZV@^y@+eOLA9-B9qVGLseC3?) z9s#f>+#AO(6Tj$&vL-;#4APaL*n-(5qNSS+gI99&yY zH`I}3*dZ|zM6&6?KH06W&41di{hZzz-WISDfiCO0yF5jG< ze-itAcx#8wyk-h;!e#vAgcIH|)DBQ+`SI0-C7vcQ&pB2MqnGAxL~4oet;I#x0i!HS zzvjmySTYmzj;6!HZYZ3iXMOJgj1naM|0kygSotZ5oN*3g8J3gFIjDg|)^H_>oR4IU zd5~P9ZpT|Bm#J^#9lT3^1*H#ZCclUG@c}->N9c*Y*vD8c)bH^FNh@_f%6@p2`V-35 zY@?@gLq#sf@-@6p9)N))H>gLW^iJ*6<1l}Soar0X}4C9Mhjpn$VP zA0_uq{nV9MP4bai?h55z=@a!GlzreHx%6*eNbaL-eK{*VM%hNr`@4I*f#Xa%Q0|Om z-yVd6QSQs+j!$wg^d^yO4$1llw-k8n1LY1*o)rX;bfO$(fz(|n*FvMHU!WXEqp4rvYn1!zF(`L-auggT8AmO5M#o9y z{!ZpKiX;ddu?bsHo_I{atJsDU@jBi_d4eH5r1XK4nZ7KoEY&!2+4rWP>}j$*a*rwZ zl@n2>uosK4gt6p4^dKH0k$r#l2tDdxlIhf*DEs*g>Y?b3a=gsMStKTuJunQ*P}byZ zJcd;`2di1P@*F_+ig29E)Z9^?&&U;{JPimSnMW=6{<1CPS%loh%bojJ@&&jM7m>^Q zEI_&DG@|UuawQ|jh+G@XRZTYTq@_IDi9)#|eT4GtNbcZ8F%HPrGvVH(L= zJdbr)j|rHFGI!~PHYj`db5}3@*7eT3{j0h9+C%oDPMnFd=g77W!*HC9b5QQKB2d2h zlDl}hb6<*b&%OvFaWQ``!6;mY%W(y+M7dLzD+;+smiyuujKu_u$2GVX6LB4`#|@Z@ zNtlc&xDmJEX54~ln2uX92QzRx?!ZixZ@aQlo<9_09_HdM+>M2pkMfOY0q(_pScFx$ zA4~869>h{Sgojc3jS4Koay*L1uoCa#ajeD@coI+HX*`4SH0ggV*5Wxlk9Bwf>ruYv zYQRRkgqQIOHeoZ$bEH<3Z@A<-S-#tnt75qlmaAX6;=P4(eR~J*;(dI85Ag{;!pHa& zJ5b&|%jca;+5bOcH@?C??7=tKk8e@lOqOR&U!i=pGzgu?-M=Jo+y^9?+YHCT%cC|@7cV-w0( z2J#(&yzSqNttj6G$k%pvupQ+~!v`qe5L3s~Q z-YS&$2IYTUz95u$|K#1g$tdsg$rp3-hMv5GCvVxU#`!4UGDqNIT!8XTocsg33RhqX z#^PFx$8{+0W6OKan=l0I7%ysUg3sjhtWRGrH=IL*$a z-jy&{Y}NTSyy{FoyYe4)>HLQB{Rh{Q`2L|nCpVq7bN_c91+Fg&{YIv{x&1#-YHkCU U9C8*O3^X`5IXDU>B}Gq03fh?sQ~&?~ diff --git a/docs/ProgrammingCryptol/appendices/Syntax.tex b/docs/ProgrammingCryptol/appendices/Syntax.tex new file mode 100644 index 00000000..085041aa --- /dev/null +++ b/docs/ProgrammingCryptol/appendices/Syntax.tex @@ -0,0 +1,387 @@ +\section{Layout}\label{layout} + +Groups of declarations are organized based on indentation. Declarations +with the same indentation belong to the same group. Lines of text that +are indented more than the beginning of a declaration belong to that +declaration, while lines of text that are indented less terminate a +group of declaration. Groups of declarations appear at the top level of +a Cryptol file, and inside \texttt{where} blocks in expressions. For +example, consider the following declaration group + +\begin{verbatim} +f x = x + y + z + where + y = x * x + z = x + y + +g y = y +\end{verbatim} + +This group has two declaration, one for \texttt{f} and one for +\texttt{g}. All the lines between \texttt{f} and \texttt{g} that are +indented more then \texttt{f} belong to \texttt{f}. + +This example also illustrates how groups of declarations may be nested +within each other. For example, the \texttt{where} expression in the +definition of \texttt{f} starts another group of declarations, +containing \texttt{y} and \texttt{z}. This group ends just before +\texttt{g}, because \texttt{g} is indented less than \texttt{y} and +\texttt{z}. + +\section{Comments}\label{comments} + +Cryptol supports block comments, which start with \texttt{/*} and end +with \texttt{*/}, and line comments, which start with \texttt{//} and +terminate at the end of the line. Block comments may be nested +arbitrarily. + +Examples: + +\begin{verbatim} +/* This is a block comment */ +// This is a line comment +/* This is a /* Nested */ block comment */ +\end{verbatim} + +\section{Identifiers}\label{identifiers} + +Cryptol identifiers consist of one or more characters. The first +character must be either an English letter or underscore (\texttt{\_}). +The following characters may be an English letter, a decimal digit, +underscore (\texttt{\_}), or a prime (\texttt{'}). Some identifiers have +special meaning in the language, so they may not be used in +programmer-defined names (see +\hyperref[keywords-and-built-in-operators]{Keywords}). + +Examples: + +\begin{verbatim} +name name1 name' longer_name +Name Name2 Name'' longerName +\end{verbatim} + +\hyperdef{}{keywords-and-built-in-operators}{\section{Keywords and +Built-in Operators}\label{keywords-and-built-in-operators}} + +The following identifiers have special meanings in Cryptol, and may not +be used for programmer defined names: + +\begin{verbatim} +Arith Inf extern inf module then +Bit True fin lg2 newtype type +Cmp else if max pragma where +False export import min property width +\end{verbatim} + +The following table contains Cryptol's operators and their associativity +with lowest precedence operators first, and highest precedence last. + +\begin{longtable}[c]{@{}ll@{}} +\toprule\addlinespace +Operator & Associativity +\\\addlinespace +\midrule\endhead +\texttt{\textbar{}\textbar{}} & left +\\\addlinespace +\texttt{\^{}} & left +\\\addlinespace +\texttt{\&\&} & left +\\\addlinespace +\texttt{-\textgreater{}} (types) & right +\\\addlinespace +\texttt{!=} \texttt{==} & not associative +\\\addlinespace +\texttt{\textgreater{}} \texttt{\textless{}} \texttt{\textless{}=} +\texttt{\textgreater{}=} & not associative +\\\addlinespace +\texttt{\#} & right +\\\addlinespace +\texttt{\textgreater{}\textgreater{}} \texttt{\textless{}\textless{}} +\texttt{\textgreater{}\textgreater{}\textgreater{}} +\texttt{\textless{}\textless{}\textless{}} & left +\\\addlinespace +\texttt{+} \texttt{-} & left +\\\addlinespace +\texttt{*} \texttt{/} \texttt{\%} & left +\\\addlinespace +\texttt{\^{}\^{}} & right +\\\addlinespace +\texttt{!} \texttt{!!} \texttt{@} \texttt{@@} & left +\\\addlinespace +(unary) \texttt{-} \texttt{\textasciitilde{}} & right +\\\addlinespace +\bottomrule +\addlinespace +\caption{Operator precedences.} +\end{longtable} + +\section{Numeric Literals}\label{numeric-literals} + +Numeric literals may be written in binary, octal, decimal, or +hexadecimal notation. The base of a literal is determined by its prefix: +\texttt{0b} for binary, \texttt{0o} for octal, no special prefix for +decimal, and \texttt{0x} for hexadecimal. + +Examples: + +\begin{verbatim} +254 // Decimal literal +0254 // Decimal literal +0b11111110 // Binary literal +0o376 // Octal literal +0xFE // Hexadecimal literal +0xfe // Hexadecimal literal +\end{verbatim} + +Numeric literals represent finite bit sequences (i.e., they have type +\texttt{{[}n{]}}). Using binary, octal, and hexadecimal notation results +in bit sequences of a fixed length, depending on the number of digits in +the literal. Decimal literals are overloaded, and so the length of the +sequence is inferred from context in which the literal is used. +Examples: + +\begin{verbatim} +0b1010 // : [4], 1 * number of digits +0o1234 // : [12], 3 * number of digits +0x1234 // : [16], 4 * number of digits + +10 // : {n}. (fin n, n >= 4) => [n] + // (need at least 4 bits) + +0 // : {n}. (fin n) => [n] +\end{verbatim} + +\section{Bits}\label{bits} + +The type \texttt{Bit} has two inhabitants: \texttt{True} and +\texttt{False}. These values may be combined using various logical +operators, or constructed as results of comparisons. + +\begin{longtable}[c]{@{}lll@{}} +\toprule\addlinespace +Operator & Associativity & Description +\\\addlinespace +\midrule\endhead +\texttt{\textbar{}\textbar{}} & left & Logical or +\\\addlinespace +\texttt{\^{}} & left & Exclusive-or +\\\addlinespace +\texttt{\&\&} & left & Logical and +\\\addlinespace +\texttt{!=} \texttt{==} & none & Not equals, equals +\\\addlinespace +\texttt{\textgreater{}} \texttt{\textless{}} \texttt{\textless{}=} +\texttt{\textgreater{}=} & none & Comparisons +\\\addlinespace +\texttt{\textasciitilde{}} & right & Logical negation +\\\addlinespace +\bottomrule +\addlinespace +\caption{Bit operations.} +\end{longtable} + +\section{If Then Else with Multiway}\label{if-then-else-with-multiway} + +\texttt{If then else} has been extended to support multi-way +conditionals. Examples: + +\begin{verbatim} +x = if y % 2 == 0 then 22 else 33 + +x = if y % 2 == 0 then 1 + | y % 3 == 0 then 2 + | y % 5 == 0 then 3 + else 7 +\end{verbatim} + +\section{Tuples and Records}\label{tuples-and-records} + +Tuples and records are used for packaging multiples values together. +Tuples are enclosed in parenthesis, while records are enclosed in +braces. The components of both tuples and records are separated by +commas. The components of tuples are expressions, while the components +of records are a label and a value separated by an equal sign. Examples: + +\begin{verbatim} +(1,2,3) // A tuple with 3 component +() // A tuple with no components + +{ x = 1, y = 2 } // A record with two fields, `x` and `y` +{} // A record with no fileds +\end{verbatim} + +The components of tuples are identified by position, while the +components of records are identified by their label, and so the ordering +of record components is not important. Examples: + +\begin{verbatim} + (1,2) == (1,2) // True + (1,2) == (2,1) // False + +{ x = 1, y = 2 } == { x = 1, y = 2 } // True +{ x = 1, y = 2 } == { y = 2, x = 1 } // True +\end{verbatim} + +The components of a record or a tuple may be accessed in two ways: via +pattern matching or by using explicit component selectors. Explicit +component selectors are written as follows: + +\begin{verbatim} +(15, 20).1 == 15 +(15, 20).2 == 20 + +{ x = 15, y = 20 }.x == 15 +\end{verbatim} + +Explicit record selectors may be used only if the program contains +sufficient type information to determine the shape of the tuple or +record. For example: + +\begin{verbatim} +type T = { sign :: Bit, number :: [15] } + +// Valid defintion: +// the type of the record is known. +isPositive : T -> Bit +isPositive x = x.sign + +// Invalid defintion: +// insufficient type information. +badDef x = x.f +\end{verbatim} + +The components of a tuple or a record may also be access by using +pattern matching. Patterns for tuples and records mirror the syntax for +constructing values: tuple patterns use parenthesis, while record +patterns use braces. Examples: + +\begin{verbatim} +getFst (x,_) = x + +distance2 { x = xPos, y = yPos } = xPos ^^ 2 + yPos ^^ 2 + +f x = fst + snd where +\end{verbatim} + +\section{Sequences}\label{sequences} + +A sequence is a fixed-length collection of element of the same type. The +type of a finite sequence of length \texttt{n}, with elements of type +\texttt{a} is \texttt{{[}n{]} a}. Often, a finite sequence of bits, +\texttt{{[}n{]} Bit}, is called a \emph{word}. We may abbreviate the +type \texttt{{[}n{]} Bit} as \texttt{{[}n{]}}. An infinite sequence with +elements of type \texttt{a} has type \texttt{{[}inf{]} a}, and +\texttt{{[}inf{]}} is an infinite stream of bits. + +\begin{verbatim} +[e1,e2,e3] // A sequence with three elements + +[t .. ] // Sequence enumerations +[t1, t2 .. ] // Step by t2 - t1 +[t1 .. t3 ] +[t1, t2 .. t3 ] +[e1 ... ] // Infinite sequence starting at e1 +[e1, e2 ... ] // Infinite sequence stepping by e2-e1 + +[ e | p11 <- e11, p12 <- e12 // Sequence comprehensions + | p21 <- e21, p22 <- e22 ] +\end{verbatim} + +Note: the bounds in finite unbounded (those with ..) sequences are type +expressions, while the bounds in bounded-finite and infinite sequences +are value expressions. + +\begin{longtable}[c]{@{}ll@{}} +\toprule\addlinespace +Operator & Description +\\\addlinespace +\midrule\endhead +\texttt{\#} & Sequence concatenation +\\\addlinespace +\texttt{\textgreater{}\textgreater{}} \texttt{\textless{}\textless{}} & +Shift (right,left) +\\\addlinespace +\texttt{\textgreater{}\textgreater{}\textgreater{}} +\texttt{\textless{}\textless{}\textless{}} & Rotate (right,left) +\\\addlinespace +\texttt{@} \texttt{!} & Access elements (front,back) +\\\addlinespace +\texttt{@@} \texttt{!!} & Access sub-sequence (front,back) +\\\addlinespace +\bottomrule +\addlinespace +\caption{Sequence operations.} +\end{longtable} + +There are also lifted point-wise operations. + +\begin{verbatim} +[p1, p2, p3, p4] // Sequence pattern +p1 # p2 // Split sequence pattern +\end{verbatim} + +\section{Functions}\label{functions} + +\begin{verbatim} +\p1 p2 -> e // Lambda expression +f p1 p2 = e // Function definition +\end{verbatim} + +\section{Local Declarations}\label{local-declarations} + +\begin{verbatim} +e where ds +\end{verbatim} + +\section{Explicit Type Instantiation}\label{explicit-type-instantiation} + +If \texttt{f} is a polymorphic value with type: + +\begin{verbatim} +f : { tyParam } + +f `{ tyParam = t } +\end{verbatim} + +\section{Demoting Numeric Types to +Values}\label{demoting-numeric-types-to-values} + +The value corresponding to a numeric type may be accessed using the +following notation: + +\begin{verbatim} +`{t} +\end{verbatim} + +Here \texttt{t} should be a type expression with numeric kind. The +resulting expression is a finite word, which is sufficiently large to +accomodate the value of the type: + +\begin{verbatim} +`{t} :: {w >= width t}. [w] +\end{verbatim} + +\section{Explicit Type Annotations}\label{explicit-type-annotations} + +Explicit type annotations may be added on expressions, patterns, and in +argument definitions. + +\begin{verbatim} +e : t + +p : t + +f (x : t) = ... +\end{verbatim} + +\section{Type Signatures}\label{type-signatures} + +\begin{verbatim} +f,g : {a,b} (fin a) => [a] b +\end{verbatim} + +\section{Type Synonym Declarations}\label{type-synonym-declarations} + +\begin{verbatim} +type T a b = [a] b +\end{verbatim} diff --git a/docs/ProgrammingCryptol/appendices/grammar.tex b/docs/ProgrammingCryptol/appendices/grammar.tex index bb38a06a..9e62b2e2 100644 --- a/docs/ProgrammingCryptol/appendices/grammar.tex +++ b/docs/ProgrammingCryptol/appendices/grammar.tex @@ -1,15 +1,16 @@ -\chapter{The Cryptol Syntax and Grammar} -\label{cha:crypt-synt-gramm} +\chapter{Cryptol Syntax} +\label{cha:crypt-synt} \todo[inline]{To be written or, preferably, generated.} %===================================================================== -\section{A Cryptol syntax summary} \label{sec:crypt-synt-summ} +\input{appendices/Syntax.tex} %===================================================================== -\section{The Cryptol Grammar} -\label{sec:cryptol-grammar} +\chapter{The Cryptol Grammar} +\label{cha:cryptol-grammar} +%% \input{appendices/CryptolEBNF.tex} %%% Local Variables: %%% mode: latex diff --git a/docs/ProgrammingCryptol/crashCourse/CrashCourse.tex b/docs/ProgrammingCryptol/crashCourse/CrashCourse.tex index c13dad49..d6790238 100644 --- a/docs/ProgrammingCryptol/crashCourse/CrashCourse.tex +++ b/docs/ProgrammingCryptol/crashCourse/CrashCourse.tex @@ -624,7 +624,7 @@ Try the following infinite enumerations: by putting $\ldots$ at the end\footnote{You can change this behavior by setting the {\tt infLength} variable, like so: {\tt Cryptol> :set infLength=10} will show the first 10 elements of infinite - lists}. Here are the responses: + sequences}. Here are the responses: \begin{Verbatim} [1, 2, 3, 4, 5, ...] [1, 3, 5, 7, 9, ...] @@ -789,7 +789,7 @@ and finally the shape description. In this case, Cryptol's {\tt [b][c][a]} is telling us that the result will be a sequence of {\tt b} things, each of which is a sequence of {\tt c} things, each of which is a word of size {\tt a}. The type constraints tell us that -{\tt a} is at least 4, because the maximum element of the list is 12, +{\tt a} is at least 4, because the maximum element of the sequence is 12, and it takes at least 4 bits to represent the value 12. The constraints are that {\tt b * c == 12}, which means we should completely cover the entire input, and that the lengths {\tt a} and @@ -1512,7 +1512,7 @@ as opposed to {\tt [0, 1, 0, 1, 0 ..]}, as one might expect\footnote{This is one of the subtle changes from Cryptol 1. The previous behavior can be achieved by dropping the first element from {\tt [1 ... ]}.}. This behavior follows from the specification that -the width of the elements of the list are derived from the width of +the width of the elements of the sequence are derived from the width of the elements in the seed, which in this case is 0. \end{Answer} @@ -1580,11 +1580,11 @@ like. So, you can talk about 2-bit quantities {\tt [2]}, as well as depending on the needs of your application. When we want to be explicit about the type of a value, we say {\tt 5:[8]}. If we do not specify a size, Cryptol's type inference engine will pick the -``appropriate'' value depending on the context.\indTypeInference +appropriate value depending on the context.\indTypeInference Recall from Section~\ref{sec:words2} that a word is, in fact, a sequence of bits. Hence, an equivalent (but verbose) way to write the -type {\tt [17]} is {\tt [1][17]Bit}, which we would say in English as -``a list of length one, whose element size is 17 bits long.'' +type {\tt [17]} is {\tt [17]Bit}, which we would say in English as +``a sequence of length 17, whose elements are Bits.'' \paragraph*{Tuples}\indTheTupleType A tuple is a heterogeneous collection of arbitrary number of @@ -1836,7 +1836,7 @@ the Cryptol primitive {\tt take}\indTake: The type of {\tt take} says that it is parameterized over {\tt front} and {\tt back}, {\tt front} must be a finite value,\indFin it takes a -list {\tt front + back} long, and returns a list {\tt front} long. +sequence {\tt front + back} long, and returns a sequence {\tt front} long. The impact of this predicate shows up when we try to take more than what is available: @@ -1876,7 +1876,7 @@ Here is another way, more direct but somewhat less satisfying: \begin{verbatim} {k} ((k - 128) * (k - 192) * (k - 256) == 0) => [k] \end{verbatim} -Note that Cryptol's types do not include {\em or} predicates, +Note that Cryptol's type constraints do not include {\em or} predicates, hence we cannot just list the possibilities in a list. \end{Answer} diff --git a/docs/ProgrammingCryptol/installation/Install.tex b/docs/ProgrammingCryptol/installation/Install.tex index 2bcfe4dd..0d77974d 100644 --- a/docs/ProgrammingCryptol/installation/Install.tex +++ b/docs/ProgrammingCryptol/installation/Install.tex @@ -255,19 +255,19 @@ aliases you have defined, along with their types. \begin{center} \begin{tabular*}{0.75\textwidth}[h]{c|c|l} \hline - | \textbf{Option} | \textbf{Default value} | \textbf{Meaning} | \\ + \textbf{Option} & \textbf{Default value} & \textbf{Meaning} \\ \hline - | \texttt{ascii} | \texttt{off} | \quad | \\ - | \texttt{base} | \texttt{} | \quad | \\ - | \texttt{debug} | \texttt{} | \quad | \\ - | \texttt{infLength} | \texttt{} | \quad | \\ - | \texttt{iteSolver} | \texttt{} | \todo[inline]{Talk to Brian.} | \\ - | \texttt{prover} | \texttt{} | \quad | \\ - | \texttt{tests} | \texttt{} | \quad | \\ - | \texttt{warnDefaulting} | \texttt{} | \quad | \\ + \texttt{ascii} & \texttt{off} & print sequences of bytes as a string \\ + \texttt{base} & \texttt{10} & numeric base for printing words \\ + \texttt{debug} & \texttt{off} & whether to print verbose debugging information \\ + \texttt{infLength} & \texttt{5} & number of elements to show from an infinite sequence \\ + \texttt{iteSolver} & \texttt{off} & whether \texttt{:prove} calls out for help on recursive functions \\ + \texttt{prover} & \texttt{cvc4} & which SMT solver to use for \texttt{:prove} \\ + \texttt{tests} & \texttt{100} & number of tests to run for \texttt{:check} \\ + \texttt{warnDefaulting} & \texttt{on} & \todo[inline]{talk to Iavor} \\ \hline - \label{tab:set_options} \end{tabular*} + \label{tab:set_options} \end{center} \paragraph*{Environment options} A variety of environment options are set through the use of the diff --git a/docs/ProgrammingCryptol/main/Cryptol.tex b/docs/ProgrammingCryptol/main/Cryptol.tex index fc8c4b0b..be33030c 100644 --- a/docs/ProgrammingCryptol/main/Cryptol.tex +++ b/docs/ProgrammingCryptol/main/Cryptol.tex @@ -14,6 +14,8 @@ \usepackage{graphicx} \usepackage{lastpage} \usepackage{makeidx} +\usepackage{longtable} +\usepackage{booktabs} \usepackage[disable]{todonotes} \usepackage[myheadings]{fullpage} \usepackage{verbatim} diff --git a/examples/Salsa20.cry b/examples/Salsa20.cry index 678bdd11..aa54abfe 100644 --- a/examples/Salsa20.cry +++ b/examples/Salsa20.cry @@ -3,6 +3,8 @@ * Distributed under the terms of the BSD3 license (see LICENSE file) */ +// see http://cr.yp.to/snuffle/spec.pdf + module Salsa20 where quarterround : [4][32] -> [4][32] diff --git a/examples/doc/Salsa20_spec.pdf b/examples/doc/Salsa20_spec.pdf deleted file mode 100644 index 2d9ac5010b25c602457839feaefeb26fb3e08306..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128071 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%0D?Tpm3rDe;-Pf_rp2vT|sFY@>(IVWdY6vVC^N z`<24rrC1O2c{qLy`yCRVL#RAT`1IWA$895<_#8H3&_7Xa%-zq9_3xUJa^lF-Y(ljX z4^v_5@I2ZRgZThu$3_57e&9`DscC-9|Ceg~FPiN?OkXxg7t;StHRk%C%D;2{SNZpU zQF{M7^6&qe`9Gi<|D*rT^$!yovbYo*_di!$|915s((n+f@qghYvqDtfx&D{RJLKL! z0`Tnr#*UWy4~8;i#s9Z7{6FII{}OzMEXM(v&-TA&{3q%%_djF*zWsmeUCRB>>HV|4 z|4Z-E);zs>5=igT-AA>WOSrYKnbleO`NScDz-Tl^BmeQRtpKQQ#`|0WFRhAG4QzDf~(cRsBxxa4vW&5f9_<9EIFHg&v;LO=;M8sWS9{KFw z+t;nO)z0g*r}p>j<0pQlW7YOm*@A+8zQEh_(~aECySt2XmYEFY>zVDR+uv8O3VAG! zJAzv`j?4+3$MrL_e=lFFkHznGTW&<%q{Ol_= zqWBCqZ}>6NQ(b!U_a9jLpHudKZ@*EQ7Yv>-PQD!)k$#)!4|t)Z`}=sgrr%vBln@60 zZtRhODnY=qUGO{5d{7-|APcNgU9};3e&x@=kn#P5O|{R?*Foz3HS)ukz(1rU=Xdbo zXbDL;5*t-xRy+m~mUEIr7-Pg;*Rk4Hy|y18ACd9{2c;HP z?dX)S1;&Ptf#gcICKq__BN|uvo(Vf%cdKgM+=cS4x%0 z3abv82r*Ib*X||twMdSMBB++ZrsMM&>dKRRp02Wio*K&Sx&Gwps|WgEAI$@--*`!4 zj4g$Zy;#6V@2A_VY%;?c9JliN=@!+}l#a-|qh^dG28~PI^P4KXwHO7ELdc>nIo@6p znMQhdWA7RLiB*$y#k)k|b_rm*ruQe!`4;NN!T5*{c*AW~_;4y8W#f}ootqKO>tEQ7 zOJ2hl?*Y&3H;D+lmLCMx?HnW30!*}&0Gj~RF83u6=3Mj8cd1kuc6|rQPs^t7u6i5W zcx^7(RXA6vPIQ4k@Ly>~iL%_kv&1-JY24=s_}`v4@TtGI4%>y=e++xQxm-yeR++qy zfZinx&8XnN-!+49$eAicv`7Ea^vkd@Uu5r3{Pj(ui2)iK4oxw%xm9(>`_JWu5;0i` z4k8!|KFgEr{p~wHwiB5VwO%xowkp>%YM3nyO|LDY`W^2NwEF#rEu$wN~a=B`&MPC^*|s3!t{$O=C@ zP_S#Zv46%#zgiv+jksKU$wA0{Rr8#ybj{~ox)@&Rfc%cgfCjb9GjI5d)X=lkxT`}EVy*Gl-rjeY<(c2K|b#>g`+YJ&^ni!T5Dq zVj;{425%O80qZZ2QRdwN8DSr^ARiQc@^S-k_gan}oZKhh*=$(4Qyd#VFs*VocuvT1 zCn6Fx8a(B4*`ZE0h#{3#TwX4TB4nsOZ9lfa<3jIQT`mdURlK8>O+?MD@kcwQxr$gq z6Jvi-0fwN;Sscywz@bZ0B4r%)zP<*2tDG$?X5G^)C4Ah(=^?1mg>^WL#c2ZaysN-` z%4xHa5VdBjQ&EzW0+_=QnDxG*Nk0hF^`>^sj}$8&CP63`_)X$oDQwoJo6 z6zG2RefIq+Dpa5uR7mMi6RYYBN3{Zog{U8{ut24*A)Wz!|5$ic9ilKGUmZ$;cF~i` zq4?X86au#U?Tlq(d7;(tDl*gucgMCE#L!Vub)xLrV$G@$K+KIBhH*43teB=~?)*+z zhp&3Ep_#>TMzTbAlnGs(5S(66=$of;vY^N6S}@&@RYJRV(GbvE7jEiqU_GLOCk8h0 zF*7CEYyfG~Spd4O0;*z+Ok`f;^er5d`9lY#P*aL0eL<*f9hK+s?(HFwRM>9%PiRYCmiIB2I* zey2>mxc~>sPq&Zo)MOF&JxIk^q#+k&)bIi#Ryy(P(1!5r`)5}tPpdN#&~o2plsGnC zc)-U|THLCXaO|BP!BNH;d*+824qDc-9=CE#I zt8QX4C81DM|EiTD92Zag1|%(d#Fav#Y!Ege)YjulxZkd9z5+aMIhCZAIJ@ zS7i{jv*_*HCM0+m0IH+8IUW>hBu455r$G}7vUbnw;gol1Coi0jdqV=uJ`Ks#TJ01& zXj_}W371KFPsxrfo>qTWBHY&~r7Bn6ux|rx-jqeH+padUzzQo(;$SqygW8n{BUs+++RkQNlX3dE3u%#GWDUzSV*6ObpWHN5ZH0dUHKv)_vnJa)${nhqd3$RJWW zV&y;@3~)!mn#wOwo88G`jgOHooCu;|VC52GYZDasaN_ZbE9367;yyMpdL5?2UcyA; zE-28{27WYYNyOX{k<(sS9#6YVkKe_8v^@Z8U`w2nr6?@=5-XuVeeD+$#2~%rOoCl! zf@Vfqbv_ha5Orvcw1v`9(9=OqsEBX;5-Ah*rKzrs!2qWN|Hy)Z^UI4j%`JZ4QWbi5 z$q^7gouzz&$ zXMvJ17(po$t6iT;e>9-HjDdA~(6Y)rKQuyJ{1TUvh4rwS7O^AJrOfZ`0p2i$oo*}# zI7Js@MR#MiOoHCjcdXrT<27*;B}vfV`AZaP7m`AiYVd3Oq2j?bl}D}Ds6DVxV)J+?@=8otRx}_~PhUk2 z3?*hJn5`1=d~fWsQ;UE=S*gL8P=t$iDperPjf#xBJP=K`75_x@C}jm z+(V_>5{&v{{e=ezmFR8Il_<3*ZcA+t*OULJGf#kh0{0zu+?mCKuVI-M zI;Ig<&KE8F&{L&?cRGc7cPRJ;5SOjH0||b$`js{O(kUBC?L*l4%2e&@SI-akVClJy zKG>Ku4Sa3(UE0E4o2Dw?E0seR;@%|%N51OsryUs|1Z5=sXar7?d(kbh-3jmCJREUZ z46kr*(-&<}0gL$Ew#p!ZuF?+gfU#;Xp!!ziTQu!g*=Yw}f~N!Alb+qcY)Ju@ z_^X3+&;VXUETmWH6oy!Dw#nt7k`>>`_1Gu0PA!e@)`*P0KjX@2J($RcaQ95OlbvyzaW7GEH7_$Kf?Po|ZcJfItKjNgx`_wD#HECSrP8p5g^)ABR#IaIJsM z2By@Qk~FEP7g{jTEzTIQy`XzfI+*R^A9y@*S78LDfxGAtqg8so_DQ>=$s<^;wkJTo zo=}TQ?|q@c%}*Z(uoKpkq&Rk;DK>Gu8@IIYWvL;l8Ij6K1sAf_Z?u&`sA&316#I7l z4jffL^n&B{38qtUQApHC91p#uyRM5ODD`aJlvmI)ex47t0@xFh9><0xl>QvC?;8{u zL>o;3#=Sv2DZfHxXGu2ANj8NZWOU2a!i(-wfxIt>b>{?)qYlxt$bqRA9cMD~3Z@07 zkwRUzkMP{9MjbC+d=cpaJ3uH4aa-{m8k*Q(@D^pWgc5Nm)#vNY`DB?*D2CxFxvTU> zHy_9M9ZEPtn^xGdk~ETWBD^qg(#cTePpEU+KbakdEPj|_? zS(DBDT@Pg#hz#cEZO@C6gs8GzOMH_E!M>EtyMG{r>G*8Sc{~+SnGA*7n=NUTh`l3P zRn)IfOp=#Nq_hjAc?f5eCm|jZcRN9ey@_H9Sq)C~NZ1~!&z>bDqZpv%Z9xQ~m3clQ zB*-r7IarA1gQ#6d5Mw-1Tp-&cW9nk-RxAQJ;ZiAj%BvUO?{V0Z8-Q@r#d27($LP!+k*`P%l9RR5 z2KzKLuMjR~YvYraKXqx9k_v1h-gKI8=F=rdm230q*m&6-CP=pC^7m>?+Uig4hi`dA zPeDU7l2G;$r{YHFeayw9!aDIqnX}0a*ZFvOlO6HVsmvc5jp*(BQXRQ={Z;oGRvIlb zkG9XZHgyWfbTVS5tNN6Ix@C#wEr|}_?aNYfB65r@`S);EHoNWJp$sIaGG(iTh(KLm zO_P_lqRif$X6z5-eI2&9Q^H#mlQoj=6JWzC@v%|PbIYVsW@D4ooE^kp5%K*dihul~ z?ZUKV=(}_-P}EByoIID(DUp}5(Jsx2q>Y8|vwtPAN~KldWalGC{)Lc~l!r&mxa@inHODz!{j2;2~dT-(8T=c zK?l&ac*y0fWcI71km4D+d+kI>D|teyGneUXnDTZ9x_S*_>74yy7+NIWm`JklQ$B7$ zs1=!JLuj3XlCv%vO%{5BJ-M&d#{~yUnYbiQC!1Kn%V-OtM~YA3T{m+JLEkZ;H!D&j z2W9t*`nemE&5pl`bKc^D2G7yG)ci1gp{K?LkQ#)3>DGnL>~(Ilyc%7^AnyBdgX-h1 z?6N6(Uz$Fxog`lM4d-(95iymjHpxcEhAPE!agIM)AIs=H*+{VNVPm+8gjwDI?Lgcb z2ye){{DY$sIydCEY-|!NND%4NrYCjm@)+CIY4zIK0pW30(TGzs>1R9DX|shP(z>0W z@-%8^%H#vo)#;$x2&aR0dQhRG77^L$NHU}a9a{OPVt>olYg5q7@o*E6dXLP%dRk_DrN%@; zcI(?UIOyN!V(J1;`sk6zd3iU=E+F=Ox!v>qnR)Gu=-U)~1q&^1G)7({}ar&qt>g0_R|0IQUuC$Of%P)G- z-%L=$zzKR!aT07|U{7N7uG>%9624rK7$C{FeTz$Ug`tjq9u~p;u=rI^f2sgx%0_V3 zz1>T)#^b=vFs*m!(kks>cl|vDBgoD=@#JuG%<6LHSj*LMk*KNR@O~QM9D@%P|cnkM6 zM$ft-8qjjqKe2#q_nC_zdaq5e012-7NSQm||B8csK%iA<8zYDbq7%x4pHnK?Zk#N4 z+Jhkpp3yvUI1~G>q>=UOnj~hB(k{jY@c#18D z^HoxZtS44mrjim)5w-Hy@Gvu6y9I%$IpC}o6E+IueG{2)!eH7OSNH&;m96NQ$ER1u!-KCLUiVhhH*7a2X(D~i ze_^~7t8yGNvd|opSI9}%m8!ZlZhocRxCZhL?q$?Vp}B|`x(S}QX&g6SZXutBJ+7$F z?1wB*lafNL~9DzZGW&mPWPqr>`L663+5D3w4sP>7|>2jA- zjx=pehAZly-uuT9iHJ=)Eghxf+{iQgpV{4Mom-t>hddg74QMsC^Vqao<4}eS|18&Z z!k<5OrNYw+|1H2ie(4MZgi&F$2e<@%JdL4^Gp;!mYG0FH(5-lQ){%QM?N!OabhYZJXNqNA533E@%fJ&SG z0_%9Rb5Mfj3RjhDfdg0_c~(ca2F+7tFXxpynvbf_A#l$R$06Hqd4|;H zeY?@od6Dc*6XHbR=^6%wG_>uB_q;J<<5rM&ZO2T+4d6+finwXz?fV|m&iQF{Az3Hh zZ%+ok)i$q_v&j5o{QZjbqoMV@8*Z9o2}3WHSn56(4&w z4M9KYmx@K_MnTh1HpNXXu`09J`BpO2xBo?%yx-G;EKyRqK-82`)kCXF0wG}6Aa}If zlG$@`i>DlN8h_Jjx&@8SO1SLOl8ZTgO8oI7epI0k7!T#Jaf!$SonUq-2bwU7#U_g2 zDipzjU}7J|XgYyFKohprC+VR9md-Yo&u!!br$`}*$13VXhu;=i7g{Oe-13Bpr!eGd z5y;#k9@nv93UVwli_;DZ^I=D=v~ex?XV?@)CEw4%e=mzDAA22XBu~Vc(%_280NE^E zR!O=WA~020Sj42#**iyIKEXBE7?lfS=@>GHgqD&Xb$@*)Lh_YEkEk&QI}Y&rOR8LA z3D_t4Xqe%34?T7}-rn+~la*~wCZaZ($#0cbmpI>a*DDq2U`IvPW7pEJTpMC`m2Xe9pKC9-JZR8@ z95${{p6n=`W9Efp#9Jc@EvHRgbzUblC9Rw@r(t$pA162aNvWM5)_Ct7S^X9ozp8tp zXJt7N^J*_1T^riyU6H9Y3G%Knco`DyoRv6ChY@dQqs*P!(H?)WE= z!TZBS#eG!Z)TrII;+lsuuU^#3YgpuY_)t>9ppsM<%Fl2jnLt9Fladg$NMhy;oh$7T z06ww<gpPd40Dq&!uR6@xe=Hf1VLiCC zpE`ntjjw5E70->9QoEX(SDd`mMkl@nxofSz`)S55lH&z%3wqnnHHY>$rX|j*9Ungn zBrgl5*JvHi6RhCb?B44t-W=(Du41Q9<%CUD2iM35Ci5eRPm3yfQ}3U4ATe*J(|nX` z9J2*I2yGo32YnG-1Y=~VNIW{-M#$gaf=geMv~vp^eAR9Q;mushwA5nn1Q&|D);302 zcS&-XfsRO>=5}LoiIZV2<#D==5H??DNXc~86MoGuzzH|MB{qF^a!#{;Cw5GDLO0Jm6*rS#{q6#bsR*#bA##)E>^y79P0pU!S zN?0S>0qLPRoM_+R{(P0Dr(S={(pdew^?IFwCtkua4&LsC=`gap&NSAUV6wrOyLnmC z0h-B~R6`SV&Rv&Mip3{gdN&cmnpYfF?Mn9HaD;%xC0toX7|@_bmO6wbl%QODqezh< z!WI~p#5TD6cE!QhK}_7Di8))QV{57_JI=Gi0m(0C_In8BU&nyF^r9NMWOEuAvDi>$ zy>V$zw0x<1x-^y=rk?b2}<8U-6KRAW9-5TIfO$D=lcSw8P21+-k5 z9r2oYYQaQ_IqQP9J#Vt=-oxup@h1VJJ7Pgm432eWBCOdMbE+?*AX4xuS9cCwR2a2Y zPiK`&X9xEumjvTwUjuGlX8UtJ21`x%?wsx-@{Xk>G5S!q7R!FxMG;9)U>0#KBxkd zz9d<`avkn_!`5S`r4Pn!=&mTOGX?&M^H5z0{YT*hW!7$U)n>!x92A=#P8|GC_QwhH zDA)$k#zlv(h)p=q1=QSTifrNzE`-A$sk+;ndaVj2g=q=;v~rSO_O)4Gv84Dz*u7q#Wy4nj;EWi$;X zA>VJL^+I?0no2a~9<^yRa=#kbD($#HA_sl z-hbxor0iW;*mF(mzrWw;CIS;Dgql@(-R$^2lbe+)GT=m5b)eBsh&QNzH zX|=4IQ;N8oHp>>T37_~3*Y$%9?{#t|`?V1#gxSH0tc|8mbR65HSt(B6tH+mJJCG=} zO~AjbtkPkwM`^x?r8v3AK!oiDOFomg>F~h*v;QRT4Z9M1)5k;Y*)N!yrzn7ISc^ zWPR_AVqluzLO@sb@|(a^Pxnc>uNQjh{0Y`jE+9RU+i+V5=Khv3t~uyrn_NC-Rl#;S z!$l$2RF+dfh}vc&h|_YoNsQ9>lWBB{kd@aeuq}BL!5JkkvVUor{@}lV5kqs$~mS_DE@7OUtQ2V0utN9<V_MGnB*{w8t&mVxeB^6-aw{w8^NUYPA?E&sBp9WITGj;(>EJucIq zE4T4mIRHPg;L?bj=-c6{0zj=he#mC>`2jGjB$AJ_g>Gp1*={Ji9+j^xjlly}gzcd<6W zrFphC9shqXEOAgYk&#zM@}L=9tLoigRXX9kV#_WQ*OU=fMJl5xcj&MQl8Ge33xP*? z8z;MxSsg^I4U!EBR;N(93O%Wl?>$fgT4rqKfK;Hi$2Gg-PCtdy2G3YE_UO!gXgho} zeC58yl`Pjsnw;;mMH8-yUPsGe*imf&pB~mB=b3~=9Iz~B9$lQrJU~m%$`E=hhAf19 z&Vs(#dy%Wp@YUK1RF}a-EeDU6+1krc-kUltn-rNUh9Wi;XAkEs8WJW#adzRvIKC+{ zvM^P?ZqdxRoGE%;sv0j9!R9G@~ zxI@x8{#COT+RT^N4ojW)4|AC>prFSncZ3{V%cf9cB!Z#^2 z>p*xR@lD}(%B7Hmt%Y)g4sUk91YXyvrXV^i^KJ-vfW=7CS~_}5vIL||xhNas>pAc) zKvJprI*bVDNF?H0<2q@DBE*Cwt?zKX{#wTaY zQ}b6)g8cK}EYg*@a_eK1qGibg6ml~3h?K&yy%be0oC)P{d)5j>dh{5vy98;PAD6G{ zDGsodQA06WNZDp%K=uR}b~y?B(rjaW9a5!JPD0ra>^E7pqwaU~#t~mjIR_!1U{sIB z_*&Tpk8@yAy&#J*GcjqNUj*ER*w}3WS0T2)!(W0)HskN`jn*3RO0hM8QX66| zUQzEJcK2q*1el`1b{9_($XJb6obZMPli(pI%^D z8(N4GiX$NEqzEDrjvABhYAi{J530m+&Uy?5*`buHT&8t2 z{Y(R+)O&zd`-iaQOZKW7%A{VXFQXQPP%F&y91|^k0c}lH^Su=KY`6n?q$Y2TK#hWR zrs`WcKX|({>c0%pqts)dcy|u&1Vz%&=-39pnRBNASE?%75nXvRp-4r{^{uK!1JoQJ zO`;Qx`;GCQ`6PlenJ3;30!v`Hyy+v$LhSD3+^Y*&%@N>MYpXPaS?QcgTdvpETGdsN zOC>gZWR(}z-H*fiRI3!=VXltZQMqP00n;a3-Qn$Dr&mO>3vjs|suTKJraPf;Sa%A_ zFDHlP(Id^6HVTlAZ_LKO=xgU>Hf{1>!i|x-ceZWp=b%gI6H6||b6}Y+a1hc2B_`LwzHqa8f!-) zz!6PimnhJ0hB_JYdo8#?M^ex8?PCp*)1N=Foqd#Pw-ZhY)VIQ+`Bo_9wiHZ*W%ZHH zD=WQlYx&8^-iD^fGmJR%J;TlV_M~Dt*F;iup`@g!z1iMw+F|qI2q#Up=?O>D!lYZ7 zu~3P|D$$z+`Q|(caP#ig6P0k#Spv)V&bBCm^)CTyNzN}|d)#d()BU_{xEZ!P7Q}la z3OX)_I3r`Iwqdl4ioCEeA5q^0a=D+}_sk%o2PxsfI$4`dP%&F(m~q*C!lSkwXx_l^ zi=4NGj9bgggI-jVc+~#z5#MejSCIEnm&Yg*4QU)5=^jpn(a!rH5YDqhcy=wzxFBc6vV?GY}fk@atHM7?>Ct+v73<{VdJ% z&z{q$007`8A#)ugJ6w7I5Oe^u;QQeNsQ?VXRG^n*1j;U%7+8Rg@HtUR#{%e)BxLzj zc%>-#B^4Baeo++Y<4p8;Ese|#fMueQw>Pj*f?{UCrIFNeRyNVMHwOB2;9Z`-7r_O3 zJQ|fhi^2euR{qEpD48^%e@=Q9Q2yo}f1)^bT*e;&;@L6(yVrmFE2w{W_0P2DT>i-E zue0CNfZ9_4Bh+7MKhJ(1pMU%FT=Qpp0pY|ywHMGVe{=J=G<-UC2G5rMS1bQZ>;7ne zIVhSR1_mOFKiBvbGO@L@=Qq}|#bp4tx`fW3ALxLHkVeo_&r09K5{O15b@b${EOab? zee;{mf5%#XA`<538Udm3-;4SmAQFQ(HyH&*%-(McEiG5g)ayAEQT7!yL-6u4B&DKM zG6UejH~2VBk%gpz)C2j{(oQxrOW9TUaPTThd>s>tUKffy$qeDh1=ZeRUE)zpk>&Ud%v5!NNesp<8SRXkeE{^Q%A< z<__|W?=09PzpcLXdt05W1u-+yPA1S|^+J{onz`^QX1jJACWV;@J)k?kOaf3&;`<6f zu2ePx+^$Yj%bf=o&#s>VL3lF?t@idSqH1K%C`zplbr2dZ(=Q!Dlpd#UR+#L z@2a@KT*8al;X0)(q^HrSaV;J%vMle&e5NCr!$R}r9lrGb(6aDeJHfo3k{<2Gw@ckj zvv)vhDOY2J?;U;GX>1N&dw7>JUG__t0<`M{(+{w+yRG{>Me@O}O66S)FTo@Bvf&wQ zSFPCs!4}+L2xw0oQDNVj;H8m#=l$qzneP9sq*YFGtzQi11LPvi-Dt6Lm#)fyG)P_~ zE;-kTAQ$w-wc>RfIdHy5z#tLW@p^XlD@mx<%ZLh&CO-mMOt%2o@DS#*I}y{3ZT2UZ ztF6N)bwmPYNa1g_ikRS!dzUP?2ryyc83wfDsZvo$j6q>O`q;-W508>o2Rd;U+-nvzrZ@B#q6TpUte=~`>MTQ*kGWV7e;TSPpRnWJ8N^y zx|7*l*>5wyQO0JU`vqJ&GjSIS=h(}W&S*Apf&i|_C=;~7q0;svhhiZ}!Hn7q7eku> zs@`ZT@e&oq+IQCqZ11)+b*8QC_&+Hm99129e`ysWAUu}u#3S%w9)mMqjO@hPcBGTDMr zadVZ7z5ao}+_GlnSlM(3!xclq*~+--rvIqdM?)0l!7PyQ;}LikZeBB-I{R@_jP^?~ zi)H`C`Ei$n?j|!`^-f(&q21fqrsFboUf-lx{?7rngE%bSP3M6<=ax#wYIBtUIwDrG zBD|)Z#)2%-+_7!wj?Z=6{P#qK@`3FTcgU-H8?;g{@#m(#G6JkoD;5n#$iATnO-!?8 zrCOcTF135Qp~Kh)vD1Hke-ZI2bnQM~&HQqnW2CZs`*!)-7$U!`KuBhj^Qih%qR8Ro z?l^sj$F3_{p!YKh;#lHeM;27a(%6xtFNkE`7k&*cQQ8&c zajGJ54hIHW4|SDI{XOffh?*O)@;$!!)~8%gF@B(oD7(O!o0N?`IeIaYYjxY1P3N^i zR}{16?PJX*;>0L9?LA(-v1UuwmpJgGQFCyGk0}L1tSfbWRD1D+u2K)7xhq%F_q_7A zXSEmPyPDXWvjiGN3_D(}Jkz|w9Lq93&75IG?`bDO$eL7lDxXs;-FIzSvqC(0iDTLyta)YH=k<*vY`#m?paiGtOgW zKrJ+|NBeF*)PV_}+#+C5C8z?vV?dlx<*5Ezn@Phzq(yzOg)NQI8iG1;kRW7jMKTeT zp*J-t*%V;WCkaku|0wp6tu?TNimPW+JG2W`wdw5?B&3?UKhbb%%K`v>NX)g@Bm(R9 zrlpOjZ*kYyYsq;WrYT$E&DU*JJ(QcFL$HcH1-oDhgM?}4VvB?~*KQ<>;pwfUY$LcM z3Z?^JeL$uWS{2i~UmGSk3dhMA(UliYj&4Y8;KMuYiWJ!ReTt9{kXY*KapeS9x_@3m z%w?neuSn*HBl&@380ndQdPe5|`qat4_`07+hM5+K7yd#rOaQ>|Nai{5ACU~*Gm@bP z;L_0nZ^caiJg5H)$|y)m@yR_y89o)Di~eUULq`ilIlsUR-4AE_2by7^{|(JB0X^<> z#($!jpB&1sdj3QJKs57%2>9dgPh|1GI0DfO)4#<<&sYzLMSzw8{-t{+AD&F^wA%^ z_%A%h{3o6G8y*8vV!!Yh;JNGmLSO$5k1;>D+E1MS6OaA**wz0SkAm$WfWhB*Hg9QZ6A$+|DzcXnUZFp&1=d4iw38%T+?8WXvfmQn>C>WFv*c^K+ zo&GkhEaQi65n+GT9!kSa+Vmj}L&&u+lm?r0?}wJqoea^>;OYHThg-W?=odr5=~IB4 zvg>LjnG9)vNw?8n)V`&ANP0QMBcu&Rmt%|_%9x|Wrb2UBIRQbDGjS9g7A#$=IB8`5 zWzKi*L#ajq#}~3Dm-&c$+E0Z6;t^-{^5)z}O_fF=3lc4x?{i#VU)&lUB`$8=na8?b zK6aUkV(@?XI*N*d5~(P{p2y{#Ma#a$ag0JOlk)Ph@^x*c!ncy9Aw!Yx#-sM{kau_; zqr6n~>}In=zYqJ+LcErq7V{al+a6(LsT@0n3mVD6IFFFtakj9@ zPDrH!U#qF83F-lPS~{X4i*HbUo;H$yQ*G|1D?cy^zsQyI;~tgC=x%O@1A{nwYCt`d z3F773(->S5vM&q7)&uMph2(G~ zh9%ro9dw%%ap%scEN;opDwZY`mmHD78zlt(E0?bMHfgR|vRra#MC-m>SU{3Q+Nuax zR0kaKvVN*-0q9+bfOI^B{#)$`jIYNwCTYd3k19d0xxAXGdUOn4R)T!EHsN7LSZB*i z+8Jmp&2zH>r9$R9Or>fq9eii$wlizWoZQ_|)L_%fgRf#=%S#8$15`O|OMhHmduV(Tv%g z149w#kB zvz$Sk=m)!PJZ02Yuu3DRRUxyZyWdORJzchXlffZHGg~5)L(8bf+Y#d1?>C4n8)bx8JQn3#;1CvZ3JHpYTw%_***WY!0Y+2gQT6fC{8 z@DipC%Gb5tJ}J5aDAv8jdPZ$^a%^vEC8jrt@PmNxX2j6vRypwBnO0wJ}i>2|}c25G~qCuFMVt6U%

i=bK@rKJ?Yb^>4f$Z#L9M(? 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 B2n+xK From f1f1991ce0fc114321b1e7da752d3e9989ec819d Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Tue, 6 May 2014 10:59:29 -0700 Subject: [PATCH 03/45] No plans to allow indented code fence blocks Support for the indented blocks isn't consistent, so it seems easier to just avoid trying to support this feature. --- src/Cryptol/Parser/Unlit.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Cryptol/Parser/Unlit.hs b/src/Cryptol/Parser/Unlit.hs index 79c34373..b005498c 100644 --- a/src/Cryptol/Parser/Unlit.hs +++ b/src/Cryptol/Parser/Unlit.hs @@ -97,7 +97,6 @@ markdown = blanks [] | otherwise = fenced (l : current) ls - -- XXX: the fences may be indented. isOpenFence l = "```cryptol" `Text.isPrefixOf` l isCloseFence l = "```" `Text.isPrefixOf` l isBlank l = Text.all isSpace l From 25f17d020263c42df37e92b6e6bacf49665687fe Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Tue, 6 May 2014 14:29:51 -0700 Subject: [PATCH 04/45] Some bug fixes related to fenced code in markdown ``` as cryptol wasn't supported, and fenced code blocks that weren't understood were having their internals processed as markdown --- src/Cryptol/Parser/Unlit.hs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Cryptol/Parser/Unlit.hs b/src/Cryptol/Parser/Unlit.hs index b005498c..548fc2e9 100644 --- a/src/Cryptol/Parser/Unlit.hs +++ b/src/Cryptol/Parser/Unlit.hs @@ -8,7 +8,7 @@ -- -- Convert a literate source file into an ordinary source file. -{-# LANGUAGE OverloadedStrings, Safe #-} +{-# LANGUAGE OverloadedStrings, Safe, PatternGuards #-} module Cryptol.Parser.Unlit ( unLit, PreProc(..), guessPreProc, knownExts ) where @@ -81,23 +81,28 @@ markdown = blanks [] blanks current [] = mk Comment current blanks current (l : ls) - | isCodeLine l = mk Comment current ++ code [l] ls - | isOpenFence l = mk Comment (l : current) ++ fenced [] ls - | isBlank l = blanks (l : current) ls - | otherwise = comment (l : current) ls + | isCodeLine l = mk Comment current ++ code [l] ls + | Just op <- isOpenFence l = mk Comment (l : current) ++ fenced op [] ls + | isBlank l = blanks (l : current) ls + | otherwise = comment (l : current) ls code current [] = mk Code current code current (l : ls) | isCodeLine l = code (l : current) ls | otherwise = mk Code current ++ comment [] (l : ls) - fenced current [] = mk Code current -- XXX should this be an error? - fenced current (l : ls) - | isCloseFence l = mk Code current ++ comment [l] ls - | otherwise = fenced (l : current) ls + fenced op current [] = mk op current -- XXX should this be an error? + fenced op current (l : ls) + | isCloseFence l = mk op current ++ comment [l] ls + | otherwise = fenced op (l : current) ls - isOpenFence l = "```cryptol" `Text.isPrefixOf` l + isOpenFence l | "```cryptol" == l' = Just Code + | "```" == l' = Just Code + | "```" `Text.isPrefixOf` l' = Just Comment + | otherwise = Nothing + where + l' = Text.dropWhile isSpace l isCloseFence l = "```" `Text.isPrefixOf` l isBlank l = Text.all isSpace l isCodeLine l = "\t" `Text.isPrefixOf` l || " " `Text.isPrefixOf` l From 28043c395c47a27a6349698f37c77381a38e8d41 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Fri, 25 Apr 2014 11:13:06 -0700 Subject: [PATCH 05/45] Only print `Loading` when the module is going to be loaded Looks like we were re-loading everything afterall! This closes #10 --- src/Cryptol/ModuleSystem/Base.hs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Cryptol/ModuleSystem/Base.hs b/src/Cryptol/ModuleSystem/Base.hs index d52e4bdd..c516378b 100644 --- a/src/Cryptol/ModuleSystem/Base.hs +++ b/src/Cryptol/ModuleSystem/Base.hs @@ -112,15 +112,18 @@ loadImport li = do let i = thing li n = P.iModule i - path <- findModule n - pm <- parseModule path - loadingImport li $ do - -- make sure that this module is the one we expect - unless (n == thing (P.mName pm)) (moduleNameMismatch n (mName pm)) + alreadyLoaded <- isLoaded n + unless alreadyLoaded $ + do path <- findModule n + pm <- parseModule path + loadingImport li $ do - _ <- loadModule pm - return () + -- make sure that this module is the one we expect + unless (n == thing (P.mName pm)) (moduleNameMismatch n (mName pm)) + + _ <- loadModule pm + return () -- | Load dependencies, typecheck, and add to the eval environment. loadModule :: P.Module -> ModuleM T.Module From a6077dd59ad2895632ac374d217060f2f10d9233 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Fri, 9 May 2014 16:26:31 -0700 Subject: [PATCH 06/45] Add more destructor functions for TypeCheck.AST.Type --- src/Cryptol/TypeCheck/AST.hs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Cryptol/TypeCheck/AST.hs b/src/Cryptol/TypeCheck/AST.hs index 636f64ca..b5f7ca1d 100644 --- a/src/Cryptol/TypeCheck/AST.hs +++ b/src/Cryptol/TypeCheck/AST.hs @@ -290,6 +290,26 @@ tIsVar ty = case tNoUser ty of TVar x -> Just x _ -> Nothing +tIsFun :: Type -> Maybe (Type, Type) +tIsFun ty = case tNoUser ty of + TCon (TC TCFun) [a, b] -> Just (a, b) + _ -> Nothing + +tIsSeq :: Type -> Maybe (Type, Type) +tIsSeq ty = case tNoUser ty of + TCon (TC TCSeq) [n, a] -> Just (n, a) + _ -> Nothing + +tIsBit :: Type -> Bool +tIsBit ty = case tNoUser ty of + TCon (TC TCBit) [] -> True + _ -> False + +tIsTuple :: Type -> Maybe [Type] +tIsTuple ty = case tNoUser ty of + TCon (TC (TCTuple _)) ts -> Just ts + _ -> Nothing + pIsFin :: Prop -> Maybe Type pIsFin ty = case tNoUser ty of TCon (PC PFin) [t1] -> Just t1 @@ -305,6 +325,16 @@ pIsEq ty = case tNoUser ty of TCon (PC PEqual) [t1,t2] -> Just (t1,t2) _ -> Nothing +pIsArith :: Prop -> Maybe Type +pIsArith ty = case tNoUser ty of + TCon (PC PArith) [t1] -> Just t1 + _ -> Nothing + +pIsCmp :: Prop -> Maybe Type +pIsCmp ty = case tNoUser ty of + TCon (PC PCmp) [t1] -> Just t1 + _ -> Nothing + pIsNumeric :: Prop -> Bool From b304eb2ef007526e6de6f21146a15df793c97e66 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Fri, 9 May 2014 16:27:07 -0700 Subject: [PATCH 07/45] New module Cryptol.TypeCheck.TypeOf has functions to compute Type of an Expr --- cryptol.cabal | 1 + src/Cryptol/TypeCheck/TypeOf.hs | 96 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/Cryptol/TypeCheck/TypeOf.hs diff --git a/cryptol.cabal b/cryptol.cabal index 2ba6e56a..b73b1dd5 100644 --- a/cryptol.cabal +++ b/cryptol.cabal @@ -82,6 +82,7 @@ library Cryptol.TypeCheck.PP, Cryptol.TypeCheck.Solve, Cryptol.TypeCheck.TypeMap, + Cryptol.TypeCheck.TypeOf, Cryptol.TypeCheck.Defaulting, Cryptol.TypeCheck.Solver.Eval, diff --git a/src/Cryptol/TypeCheck/TypeOf.hs b/src/Cryptol/TypeCheck/TypeOf.hs new file mode 100644 index 00000000..d7f12277 --- /dev/null +++ b/src/Cryptol/TypeCheck/TypeOf.hs @@ -0,0 +1,96 @@ +-- | +-- Module : $Header$ +-- Copyright : (c) 2014 Galois, Inc. +-- License : BSD3 +-- Maintainer : cryptol@galois.com +-- Stability : provisional +-- Portability : portable + +{-# LANGUAGE Safe #-} +{-# LANGUAGE ViewPatterns #-} +module Cryptol.TypeCheck.TypeOf + ( fastTypeOf + , fastSchemaOf + ) where + +import Cryptol.TypeCheck.AST +import Cryptol.TypeCheck.Subst +import Cryptol.Prims.Types (typeOf) + +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (fromJust) + +-- | Given a typing environment and an expression, compute the type of +-- the expression as quickly as possible, assuming that the expression +-- is well formed with correct type annotations. +fastTypeOf :: Map QName Schema -> Expr -> Type +fastTypeOf tyenv expr = + case expr of + -- Monomorphic fragment + EList es t -> tSeq (tNum (length es)) t + ETuple es -> tTuple (map (fastTypeOf tyenv) es) + ERec fields -> tRec [ (name, fastTypeOf tyenv e) | (name, e) <- fields ] + ESel e sel -> typeSelect (fastTypeOf tyenv e) sel + EIf _ e _ -> fastTypeOf tyenv e + EComp t _ _ -> t + EAbs x t e -> tFun t (fastTypeOf (Map.insert x (Forall [] [] t) tyenv) e) + EApp e _ -> case tIsFun (fastTypeOf tyenv e) of + Just (_, t) -> t + Nothing -> error "internal error" + ECast _ t -> t + -- Polymorphic fragment + ECon {} -> polymorphic + EVar {} -> polymorphic + ETAbs {} -> polymorphic + ETApp {} -> polymorphic + EProofAbs {} -> polymorphic + EProofApp {} -> polymorphic + EWhere {} -> polymorphic + where + polymorphic = + case fastSchemaOf tyenv expr of + Forall [] [] ty -> ty + _ -> error "internal error: unexpected polymorphic type" + +fastSchemaOf :: Map QName Schema -> Expr -> Schema +fastSchemaOf tyenv expr = + case expr of + -- Polymorphic fragment + ECon econ -> typeOf econ + EVar x -> fromJust (Map.lookup x tyenv) + ETAbs tparam e -> case fastSchemaOf tyenv e of + Forall tparams props ty -> Forall (tparam : tparams) props ty + ETApp e t -> case fastSchemaOf tyenv e of + Forall (tparam : tparams) props ty -> Forall tparams (apSubst s props) (apSubst s ty) + where s = singleSubst (tpVar tparam) t + _ -> error "internal error" + EProofAbs p e -> case fastSchemaOf tyenv e of + Forall [] props ty -> Forall [] (p : props) ty + _ -> error "internal error" + EProofApp e -> case fastSchemaOf tyenv e of + Forall [] (_ : props) ty -> Forall [] props ty + _ -> error "internal error" + EWhere e dgs -> fastSchemaOf (foldr addDeclGroup tyenv dgs) e + where addDeclGroup (Recursive ds) = flip (foldr addDecl) ds + addDeclGroup (NonRecursive d) = addDecl d + addDecl d = Map.insert (dName d) (dSignature d) + -- Monomorphic fragment + EList {} -> monomorphic + ETuple {} -> monomorphic + ERec {} -> monomorphic + ESel {} -> monomorphic + EIf {} -> monomorphic + EComp {} -> monomorphic + EApp {} -> monomorphic + EAbs {} -> monomorphic + ECast {} -> monomorphic + where + monomorphic = Forall [] [] (fastTypeOf tyenv expr) + +-- | Yields the return type of the selector on the given argument type. +typeSelect :: Type -> Selector -> Type +typeSelect (TCon _tctuple ts) (TupleSel i _) = ts !! (i - 1) +typeSelect (TRec fields) (RecordSel n _) = fromJust (lookup n fields) +typeSelect (TCon _tcseq [_, a]) (ListSel _ _) = a +typeSelect _ _ = error "internal error: typeSelect" From 682c130b11e20893996cffaf08709d7e5bf389e3 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 13 May 2014 13:38:55 -0700 Subject: [PATCH 08/45] Add "fin" constraint on index size for (@), (!) and (!!), like (@@) has. Prior to this changeset, any of the following three expressions [1,2,3] @ (zero : [inf]) [1,2,3] ! (zero : [inf]) [1,2,3] !! [zero : [inf]] would produce the following error: Location: [Eval] fromVWord Message: not a word [False, False, False, False, False, ...] --- src/Cryptol/Prims/Eval.hs | 2 +- src/Cryptol/Prims/Types.hs | 12 ++++++------ src/Cryptol/Symbolic/Prims.hs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Cryptol/Prims/Eval.hs b/src/Cryptol/Prims/Eval.hs index 75bae254..0b009903 100644 --- a/src/Cryptol/Prims/Eval.hs +++ b/src/Cryptol/Prims/Eval.hs @@ -665,7 +665,7 @@ indexPrimOne :: (Maybe Integer -> [Value] -> Integer -> Value) -> Value indexPrimOne op = tlam $ \ n -> tlam $ \ _a -> - tlam $ \ _m -> + tlam $ \ _i -> lam $ \ l -> lam $ \ r -> let vs = fromSeq l diff --git a/src/Cryptol/Prims/Types.hs b/src/Cryptol/Prims/Types.hs index abc5665a..611ef5f8 100644 --- a/src/Cryptol/Prims/Types.hs +++ b/src/Cryptol/Prims/Types.hs @@ -218,29 +218,29 @@ typeOf econ = ECAt -> let n = var 0 KNum a = var 1 KType - m = var 2 KNum - in forAll [n,a,m] [] (tSeq n a `tFun` tWord m `tFun` a) + i = var 2 KNum + in forAll [n,a,i] [ pFin i ] (tSeq n a `tFun` tWord i `tFun` a) ECAtRange -> let n = var 0 KNum a = var 1 KType m = var 2 KNum i = var 3 KNum - in forAll [n,a,m,i] [pFin i] + in forAll [n,a,m,i] [ pFin i ] (tSeq n a `tFun` tSeq m (tWord i) `tFun` tSeq m a) ECAtBack -> let n = var 0 KNum a = var 1 KType - m = var 2 KNum - in forAll [n,a,m] [ pFin n ] (tSeq n a `tFun` tWord m `tFun` a) + i = var 2 KNum + in forAll [n,a,i] [ pFin n, pFin i ] (tSeq n a `tFun` tWord i `tFun` a) ECAtRangeBack -> let n = var 0 KNum a = var 1 KType m = var 2 KNum i = var 3 KNum - in forAll [n,a,m,i] [ pFin n ] + in forAll [n,a,m,i] [ pFin n, pFin i ] (tSeq n a `tFun` tSeq m (tWord i) `tFun` tSeq m a) -- {a,b,c} (fin a) => [a+b] c -> ([a]c,[b]c) diff --git a/src/Cryptol/Symbolic/Prims.hs b/src/Cryptol/Symbolic/Prims.hs index 0c866840..eaccffbe 100644 --- a/src/Cryptol/Symbolic/Prims.hs +++ b/src/Cryptol/Symbolic/Prims.hs @@ -326,7 +326,7 @@ evalECon econ = VCons ys <$> delay (transp t' yss) transp (numTValue b) th - ECAt -> -- {n,a,m} [n]a -> [m] -> a + ECAt -> -- {n,a,i} (fin i) => [n]a -> [i] -> a tlam $ \_ -> tlam $ \a -> tlam $ \_ -> @@ -347,7 +347,7 @@ evalECon econ = f th = delay (selectV (\i -> nthV err th1 i) th) mapV f th2 - ECAtBack -> -- {n,a,m} (fin n) => [n]a -> [m] -> a + ECAtBack -> -- {n,a,i} (fin n, fin i) => [n]a -> [i] -> a tlam $ \(finTValue -> n) -> tlam $ \a -> tlam $ \_ -> From 0ee396d434269e0bee0def34fc3201d38e4a1d22 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Thu, 15 May 2014 14:41:22 -0700 Subject: [PATCH 09/45] Pretty printer for Cryptol.Parser.AST.Expr parenthesizes infix operators. (The printer for Cryptol.TypeCheck.AST.Expr already does this.) Previously: Cryptol> :t (+) + : {a} (Arith a) => a -> a -> a Now: Cryptol> :t (+) (+) : {a} (Arith a) => a -> a -> a --- src/Cryptol/Parser/AST.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cryptol/Parser/AST.hs b/src/Cryptol/Parser/AST.hs index c006a8fb..98d2cd81 100644 --- a/src/Cryptol/Parser/AST.hs +++ b/src/Cryptol/Parser/AST.hs @@ -648,7 +648,7 @@ instance PP Expr where -- atoms EVar x -> pp x - ECon x -> pp x + ECon x -> ppPrefix x ELit x -> pp x ETuple es -> parens (commaSep (map pp es)) ERecord fs -> braces (commaSep (map (ppNamed "=") fs)) From 62cdfd453f9968c72a2a8c96b848f2c281dc5834 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 27 May 2014 15:46:42 -0700 Subject: [PATCH 10/45] Cryptol REPL prints a hint if the user omits "=" on a ":set" command. Old behavior: Cryptol> :set base 16 base 16 = 10 New behavior: Cryptol> :set base 16 Unknown user option: `base 16` Did you mean: `:set base = 16`? --- cryptol/REPL/Command.hs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cryptol/REPL/Command.hs b/cryptol/REPL/Command.hs index 79a6bb84..94b6dc35 100644 --- a/cryptol/REPL/Command.hs +++ b/cryptol/REPL/Command.hs @@ -473,7 +473,7 @@ browseVars pfx = do setOptionCmd :: String -> REPL () setOptionCmd str - | Just value <- mbValue = setUser (mkKey key) value + | Just value <- mbValue = setUser key value | null key = mapM_ (describe . optName) (leaves userOptions) | otherwise = describe key where @@ -483,18 +483,17 @@ setOptionCmd str _ : stuff -> Just (trim stuff) _ -> Nothing - - - mkKey = takeWhile (not . isSpace) - describe k = do - ev <- tryGetUser (mkKey k) + ev <- tryGetUser k io $ case ev of Just (EnvString s) -> putStrLn (k ++ " = " ++ s) Just (EnvNum n) -> putStrLn (k ++ " = " ++ show n) Just (EnvBool True) -> putStrLn (k ++ " = on") Just (EnvBool False) -> putStrLn (k ++ " = off") - Nothing -> putStrLn ("Unknown user option: `" ++ k ++ "`") + Nothing -> do putStrLn ("Unknown user option: `" ++ k ++ "`") + when (any isSpace k) $ do + let (k1, k2) = break isSpace k + putStrLn ("Did you mean: `:set " ++ k1 ++ " =" ++ k2 ++ "`?") helpCmd :: String -> REPL () From f0ab651157d45e99a878a8b12f919cc5cea649cb Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 15 Jul 2014 14:32:14 -0700 Subject: [PATCH 11/45] :sat and :prove print counterexamples in the user-configured number base --- cryptol/REPL/Command.hs | 6 ++++-- src/Cryptol/Symbolic.hs | 10 ++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cryptol/REPL/Command.hs b/cryptol/REPL/Command.hs index 94b6dc35..d959700f 100644 --- a/cryptol/REPL/Command.hs +++ b/cryptol/REPL/Command.hs @@ -321,7 +321,8 @@ proveCmd str = do EnvString proverName <- getUser "prover" EnvBool iteSolver <- getUser "iteSolver" EnvBool verbose <- getUser "debug" - liftModuleCmd $ Cryptol.Symbolic.prove (proverName, iteSolver, verbose, str) (expr, schema) + ppOpts <- getPPValOpts + liftModuleCmd $ Cryptol.Symbolic.prove (proverName, iteSolver, verbose, ppOpts, str) (expr, schema) satCmd :: String -> REPL () satCmd str = do @@ -330,7 +331,8 @@ satCmd str = do EnvString proverName <- getUser "prover" EnvBool iteSolver <- getUser "iteSolver" EnvBool verbose <- getUser "debug" - liftModuleCmd $ Cryptol.Symbolic.sat (proverName, iteSolver, verbose, str) (expr, schema) + ppOpts <- getPPValOpts + liftModuleCmd $ Cryptol.Symbolic.sat (proverName, iteSolver, verbose, ppOpts, str) (expr, schema) specializeCmd :: String -> REPL () specializeCmd str = do diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 10e2ab00..44b60ca1 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -46,8 +46,8 @@ lookupProver "yices" = SBV.yices lookupProver "z3" = SBV.z3 lookupProver s = error $ "invalid prover: " ++ s -prove :: (String, Bool, Bool, String) -> (Expr, Schema) -> M.ModuleCmd () -prove (proverName, useSolverIte, verbose, input) (expr, schema) = protectStack useSolverIte $ \modEnv -> do +prove :: (String, Bool, Bool, Eval.PPOpts, String) -> (Expr, Schema) -> M.ModuleCmd () +prove (proverName, useSolverIte, verbose, ppOpts, input) (expr, schema) = protectStack useSolverIte $ \modEnv -> do let extDgs = allDeclGroups modEnv let prover = lookupProver proverName case predArgTypes schema of @@ -73,7 +73,6 @@ prove (proverName, useSolverIte, verbose, input) (expr, schema) = protectStack u SBV.ThmResult (SBV.Satisfiable {}) -> do let Right (_, cws) = SBV.getModel result let (vs, _) = parseValues ts cws - let ppOpts = Eval.defaultPPOpts let doc = hsep (text input : map (pp . Eval.WithBase ppOpts) vs) print $ doc <+> text "= False" SBV.ThmResult (SBV.Unsatisfiable {}) -> do @@ -82,8 +81,8 @@ prove (proverName, useSolverIte, verbose, input) (expr, schema) = protectStack u print result return (Right ((), modEnv), []) -sat :: (String, Bool, Bool, String) -> (Expr, Schema) -> M.ModuleCmd () -sat (proverName, useSolverIte, verbose, input) (expr, schema) = protectStack useSolverIte $ \modEnv -> do +sat :: (String, Bool, Bool, Eval.PPOpts, String) -> (Expr, Schema) -> M.ModuleCmd () +sat (proverName, useSolverIte, verbose, ppOpts, input) (expr, schema) = protectStack useSolverIte $ \modEnv -> do let extDgs = allDeclGroups modEnv let prover = lookupProver proverName case predArgTypes schema of @@ -109,7 +108,6 @@ sat (proverName, useSolverIte, verbose, input) (expr, schema) = protectStack use SBV.SatResult (SBV.Satisfiable {}) -> do let Right (_, cws) = SBV.getModel result let (vs, _) = parseValues ts cws - let ppOpts = Eval.defaultPPOpts let doc = hsep (text input : map (pp . Eval.WithBase ppOpts) vs) print $ doc <+> text "= True" SBV.SatResult (SBV.Unsatisfiable {}) -> do From ee8685e4f283968b236b9328b43b5d72eb49cee6 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 15 Jul 2014 15:35:37 -0700 Subject: [PATCH 12/45] REPL handles printing of :sat/prove results; parenthesize predicates as appropriate --- cryptol/REPL/Command.hs | 16 ++++++++++-- src/Cryptol/Symbolic.hs | 55 +++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/cryptol/REPL/Command.hs b/cryptol/REPL/Command.hs index d959700f..5ada7a3a 100644 --- a/cryptol/REPL/Command.hs +++ b/cryptol/REPL/Command.hs @@ -321,8 +321,14 @@ proveCmd str = do EnvString proverName <- getUser "prover" EnvBool iteSolver <- getUser "iteSolver" EnvBool verbose <- getUser "debug" + result <- liftModuleCmd $ Cryptol.Symbolic.prove (proverName, iteSolver, verbose) (expr, schema) ppOpts <- getPPValOpts - liftModuleCmd $ Cryptol.Symbolic.prove (proverName, iteSolver, verbose, ppOpts, str) (expr, schema) + case result of + Left msg -> io $ putStrLn msg + Right Nothing -> io $ putStrLn "Q.E.D." + Right (Just vs) -> io $ print $ hsep (doc : docs) <+> text "= False" + where doc = ppPrec 3 parseExpr -- function application has precedence 3 + docs = map (pp . E.WithBase ppOpts) vs satCmd :: String -> REPL () satCmd str = do @@ -331,8 +337,14 @@ satCmd str = do EnvString proverName <- getUser "prover" EnvBool iteSolver <- getUser "iteSolver" EnvBool verbose <- getUser "debug" + result <- liftModuleCmd $ Cryptol.Symbolic.sat (proverName, iteSolver, verbose) (expr, schema) ppOpts <- getPPValOpts - liftModuleCmd $ Cryptol.Symbolic.sat (proverName, iteSolver, verbose, ppOpts, str) (expr, schema) + case result of + Left msg -> io $ putStrLn msg + Right Nothing -> io $ putStrLn "Unsatisfiable." + Right (Just vs) -> io $ print $ hsep (doc : docs) <+> text "= True" + where doc = ppPrec 3 parseExpr -- function application has precedence 3 + docs = map (pp . E.WithBase ppOpts) vs specializeCmd :: String -> REPL () specializeCmd str = do diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 44b60ca1..93924283 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -46,13 +46,12 @@ lookupProver "yices" = SBV.yices lookupProver "z3" = SBV.z3 lookupProver s = error $ "invalid prover: " ++ s -prove :: (String, Bool, Bool, Eval.PPOpts, String) -> (Expr, Schema) -> M.ModuleCmd () -prove (proverName, useSolverIte, verbose, ppOpts, input) (expr, schema) = protectStack useSolverIte $ \modEnv -> do +prove :: (String, Bool, Bool) -> (Expr, Schema) -> M.ModuleCmd (Either String (Maybe [Eval.Value])) +prove (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolverIte $ \modEnv -> do let extDgs = allDeclGroups modEnv let prover = lookupProver proverName case predArgTypes schema of - Left msg -> do putStrLn msg - return (Right ((), modEnv), []) + Left msg -> return (Right (Left msg, modEnv), []) Right ts -> do when verbose $ putStrLn "Simulating..." let runE = do args <- mapM forallFinType ts env <- evalDecls emptyEnv extDgs @@ -69,25 +68,20 @@ prove (proverName, useSolverIte, verbose, ppOpts, input) (expr, schema) = protec b <- runTheMonad runE simenv when verbose $ liftIO $ putStrLn $ "Calling " ++ proverName ++ "..." return b - case result of - SBV.ThmResult (SBV.Satisfiable {}) -> do - let Right (_, cws) = SBV.getModel result - let (vs, _) = parseValues ts cws - let doc = hsep (text input : map (pp . Eval.WithBase ppOpts) vs) - print $ doc <+> text "= False" - SBV.ThmResult (SBV.Unsatisfiable {}) -> do - putStrLn "Q.E.D." - SBV.ThmResult _ -> do - print result - return (Right ((), modEnv), []) + let solution = case result of + SBV.ThmResult (SBV.Satisfiable {}) -> Right (Just vs) + where Right (_, cws) = SBV.getModel result + (vs, _) = parseValues ts cws + SBV.ThmResult (SBV.Unsatisfiable {}) -> Right Nothing + SBV.ThmResult _ -> Left (show result) + return (Right (solution, modEnv), []) -sat :: (String, Bool, Bool, Eval.PPOpts, String) -> (Expr, Schema) -> M.ModuleCmd () -sat (proverName, useSolverIte, verbose, ppOpts, input) (expr, schema) = protectStack useSolverIte $ \modEnv -> do +sat :: (String, Bool, Bool) -> (Expr, Schema) -> M.ModuleCmd (Either String (Maybe [Eval.Value])) +sat (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolverIte $ \modEnv -> do let extDgs = allDeclGroups modEnv let prover = lookupProver proverName case predArgTypes schema of - Left msg -> do putStrLn msg - return (Right ((), modEnv), []) + Left msg -> return (Right (Left msg, modEnv), []) Right ts -> do when verbose $ putStrLn "Simulating..." let runE = do args <- mapM existsFinType ts env <- evalDecls emptyEnv extDgs @@ -104,19 +98,15 @@ sat (proverName, useSolverIte, verbose, ppOpts, input) (expr, schema) = protectS b <- runTheMonad runE simenv when verbose $ liftIO $ putStrLn $ "Calling " ++ proverName ++ "..." return b - case result of - SBV.SatResult (SBV.Satisfiable {}) -> do - let Right (_, cws) = SBV.getModel result - let (vs, _) = parseValues ts cws - let doc = hsep (text input : map (pp . Eval.WithBase ppOpts) vs) - print $ doc <+> text "= True" - SBV.SatResult (SBV.Unsatisfiable {}) -> do - putStrLn "Unsatisfiable." - SBV.SatResult _ -> do - print result - return (Right ((), modEnv), []) + let solution = case result of + SBV.SatResult (SBV.Satisfiable {}) -> Right (Just vs) + where Right (_, cws) = SBV.getModel result + (vs, _) = parseValues ts cws + SBV.SatResult (SBV.Unsatisfiable {}) -> Right Nothing + SBV.SatResult _ -> Left (show result) + return (Right (solution, modEnv), []) -protectStack :: Bool -> M.ModuleCmd () -> M.ModuleCmd () +protectStack :: Bool -> M.ModuleCmd (Either String a) -> M.ModuleCmd (Either String a) protectStack usingITE cmd modEnv = X.catchJust isOverflow (cmd modEnv) handler where isOverflow X.StackOverflow = Just () isOverflow _ = Nothing @@ -124,8 +114,7 @@ protectStack usingITE cmd modEnv = X.catchJust isOverflow (cmd modEnv) handler | otherwise = msgBase ++ "\n" ++ "Using ':set iteSolver=on' might help." msgBase = "Symbolic evaluation failed to terminate." - handler () = do putStrLn msg - return (Right ((), modEnv), []) + handler () = return (Right (Left msg, modEnv), []) parseValues :: [FinType] -> [SBV.CW] -> ([Eval.Value], [SBV.CW]) parseValues [] cws = ([], cws) From 4f28f4bfed1c002c79eea149c85053526ac1db92 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 15 Jul 2014 16:26:37 -0700 Subject: [PATCH 13/45] Update regression tests for :prove and :sat output As of ee8685e4f283968b236b9328b43b5d72eb49cee6, :sat and :prove now parse and then pretty print the given predicate with appropriate parenthesization. --- tests/regression/r03.icry.stdout | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regression/r03.icry.stdout b/tests/regression/r03.icry.stdout index 3a56f25e..7d48a33f 100644 --- a/tests/regression/r03.icry.stdout +++ b/tests/regression/r03.icry.stdout @@ -1,5 +1,5 @@ Loading module Cryptol Loading module Cryptol Loading module r03 -nQueens:Solution 5 [4, 2, 0, 3, 1] = True -(nQueensProve:Solution 4) [2, 0, 3, 1] = False +(nQueens : Solution 5) [4, 2, 0, 3, 1] = True +(nQueensProve : Solution 4) [2, 0, 3, 1] = False From ca14cff93aa497fa45f98f48668342e0bb84143b Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 15 Jul 2014 16:43:20 -0700 Subject: [PATCH 14/45] Pretty-print hex literals in parser AST using lowercase "abcdef". This makes the Cryptol.Parser.AST pretty printer consistent with the one for values in Cryptol.Eval.Value. --- src/Cryptol/Parser/AST.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cryptol/Parser/AST.hs b/src/Cryptol/Parser/AST.hs index 98d2cd81..d8f24ffc 100644 --- a/src/Cryptol/Parser/AST.hs +++ b/src/Cryptol/Parser/AST.hs @@ -571,7 +571,7 @@ ppNumLit n info = PolyLit w -> text "<|" <+> poly w <+> text "|>" where pad base pref w = - let txt = showIntAtBase base ("0123456789ABCDEF" !!) n "" + let txt = showIntAtBase base ("0123456789abcdef" !!) n "" in text pref <> text (replicate (w - length txt) '0') <> text txt poly w = let (res,deg) = bits Nothing [] 0 n From 733c7932c52e33f53ef22eec458c2a05c4b76deb Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 10:38:21 -0700 Subject: [PATCH 15/45] Fix compiler warnings --- src/Cryptol/Symbolic/BitVector.hs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/Cryptol/Symbolic/BitVector.hs b/src/Cryptol/Symbolic/BitVector.hs index 5a0d9707..f924a96d 100644 --- a/src/Cryptol/Symbolic/BitVector.hs +++ b/src/Cryptol/Symbolic/BitVector.hs @@ -13,12 +13,7 @@ module Cryptol.Symbolic.BitVector where import Data.Bits import Control.Monad (replicateM) -import Control.Monad.IO.Class -import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT) -import Data.Bits -import Data.IORef import System.Random -import Test.QuickCheck (quickCheck) import Data.SBV.Bridge.Yices import Data.SBV.Internals @@ -137,23 +132,23 @@ cat x y = SBV k (Right (cache z)) newExpr st k (SBVApp Join [xsw, ysw]) randomSBVBitVector :: Int -> IO (SBV BitVector) -randomSBVBitVector width = do - bs <- replicateM width randomIO +randomSBVBitVector w = do + bs <- replicateM w randomIO let x = sum [ bit i | (i, b) <- zip [0..] bs, b ] - return (literal (bv width x)) + return (literal (bv w x)) mkSymBitVector :: Maybe Quantifier -> Maybe String -> Int -> Symbolic (SBV BitVector) -mkSymBitVector mbQ mbNm width = - mkSymSBVWithRandom (randomSBVBitVector width) mbQ (KBounded False width) mbNm +mkSymBitVector mbQ mbNm w = + mkSymSBVWithRandom (randomSBVBitVector w) mbQ (KBounded False w) mbNm forallBV :: String -> Int -> Symbolic (SBV BitVector) -forallBV name width = mkSymBitVector (Just ALL) (Just name) width +forallBV nm w = mkSymBitVector (Just ALL) (Just nm) w forallBV_ :: Int -> Symbolic (SBV BitVector) -forallBV_ width = mkSymBitVector (Just ALL) Nothing width +forallBV_ w = mkSymBitVector (Just ALL) Nothing w existsBV :: String -> Int -> Symbolic (SBV BitVector) -existsBV name width = mkSymBitVector (Just EX) (Just name) width +existsBV nm w = mkSymBitVector (Just EX) (Just nm) w existsBV_ :: Int -> Symbolic (SBV BitVector) -existsBV_ width = mkSymBitVector (Just EX) Nothing width +existsBV_ w = mkSymBitVector (Just EX) Nothing w From 010d930638cef32aafbb76c8a6e39b335ab0f9e8 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 11:28:40 -0700 Subject: [PATCH 16/45] Optimize symbolic "extract" to produce literal value if result has width 0 --- src/Cryptol/Symbolic/BitVector.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cryptol/Symbolic/BitVector.hs b/src/Cryptol/Symbolic/BitVector.hs index f924a96d..5b007749 100644 --- a/src/Cryptol/Symbolic/BitVector.hs +++ b/src/Cryptol/Symbolic/BitVector.hs @@ -106,6 +106,7 @@ instance SDivisible (SBV BitVector) where extract :: Int -> Int -> SWord -> SWord extract i j x = case x of + _ | i < j -> SBV k (Left (CW k (CWInteger 0))) SBV _ (Left cw) -> case cw of CW _ (CWInteger v) -> SBV k (Left (normCW (CW k (CWInteger (v `shiftR` j))))) From 11d5c884b7d555d92fc35762c49a4a43332ae914 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 14:59:33 -0700 Subject: [PATCH 17/45] prove/sat commands avoid generating SBV bitvector variables of zero width --- src/Cryptol/Symbolic.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 93924283..df3ea1d3 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -125,6 +125,7 @@ parseValues (t : ts) cws = (v : vs, cws'') parseValue :: FinType -> [SBV.CW] -> (Eval.Value, [SBV.CW]) parseValue FTBit [] = error "parseValue" parseValue FTBit (cw : cws) = (Eval.VBit (SBV.fromCW cw), cws) +parseValue (FTSeq 0 FTBit) cws = (Eval.VWord 0 0, cws) parseValue (FTSeq n FTBit) (cw : cws) | SBV.isBounded cw = (Eval.VWord (toInteger n) (SBV.fromCW cw), cws) | otherwise = error "parseValue" @@ -180,6 +181,7 @@ forallFinType :: FinType -> TheMonad Value forallFinType ty = case ty of FTBit -> VBit <$> liftSymbolic SBV.forall_ + FTSeq 0 FTBit -> return $ VWord (SBV.literal (bv 0 0)) FTSeq n FTBit -> VWord <$> liftSymbolic (forallBV_ n) FTSeq n t -> vSeq <$> replicateM n (mkThunk t) FTTuple ts -> VTuple <$> mapM mkThunk ts @@ -192,6 +194,7 @@ existsFinType :: FinType -> TheMonad Value existsFinType ty = case ty of FTBit -> VBit <$> liftSymbolic SBV.exists_ + FTSeq 0 FTBit -> return $ VWord (SBV.literal (bv 0 0)) FTSeq n FTBit -> VWord <$> liftSymbolic (existsBV_ n) FTSeq n t -> vSeq <$> replicateM n (mkThunk t) FTTuple ts -> VTuple <$> mapM mkThunk ts From db08bfafa9a67e20fc3ddc5157075cc471ea2b04 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 15:23:34 -0700 Subject: [PATCH 18/45] Merge changes from SBV release version 3.1 --- cryptol.cabal | 5 +- sbv/Data/SBV.hs | 191 +++++++++++++++- sbv/Data/SBV/BitVectors/AlgReals.hs | 2 +- sbv/Data/SBV/BitVectors/Data.hs | 279 ++++++++++++++++++----- sbv/Data/SBV/BitVectors/Model.hs | 324 +++++++++++++++++---------- sbv/Data/SBV/BitVectors/PrettyNum.hs | 1 - sbv/Data/SBV/BitVectors/SignCast.hs | 2 + sbv/Data/SBV/Bridge/Boolector.hs | 4 +- sbv/Data/SBV/Bridge/CVC4.hs | 2 + sbv/Data/SBV/Bridge/MathSAT.hs | 107 +++++++++ sbv/Data/SBV/Bridge/Yices.hs | 2 + sbv/Data/SBV/Bridge/Z3.hs | 2 + sbv/Data/SBV/Compilers/C.hs | 9 +- sbv/Data/SBV/Compilers/CodeGen.hs | 6 +- sbv/Data/SBV/Internals.hs | 7 +- sbv/Data/SBV/Provers/Boolector.hs | 4 +- sbv/Data/SBV/Provers/CVC4.hs | 4 +- sbv/Data/SBV/Provers/MathSAT.hs | 92 ++++++++ sbv/Data/SBV/Provers/Prover.hs | 227 ++++++------------- sbv/Data/SBV/Provers/SExpr.hs | 7 +- sbv/Data/SBV/Provers/Yices.hs | 2 +- sbv/Data/SBV/Provers/Z3.hs | 34 +-- sbv/Data/SBV/SMT/SMT.hs | 244 ++++++++++---------- sbv/Data/SBV/SMT/SMTLib.hs | 6 +- sbv/Data/SBV/SMT/SMTLib1.hs | 8 +- sbv/Data/SBV/SMT/SMTLib2.hs | 63 ++++-- sbv/Data/SBV/Tools/ExpectedValue.hs | 8 +- sbv/Data/SBV/Tools/GenTest.hs | 16 +- sbv/Data/SBV/Tools/Optimize.hs | 2 +- sbv/Data/SBV/Tools/Polynomial.hs | 7 +- 30 files changed, 1129 insertions(+), 538 deletions(-) create mode 100644 sbv/Data/SBV/Bridge/MathSAT.hs create mode 100644 sbv/Data/SBV/Provers/MathSAT.hs diff --git a/cryptol.cabal b/cryptol.cabal index b73b1dd5..91ec2a39 100644 --- a/cryptol.cabal +++ b/cryptol.cabal @@ -18,6 +18,7 @@ flag static library Build-depends: base >= 4.6, array >= 0.4, + async >= 2.0, containers >= 0.5, deepseq >= 1.3, directory >= 1.2, @@ -25,7 +26,7 @@ library filepath >= 1.3, GraphSCC >= 1.0.4, monadLib >= 3.7.2, - mtl >= 2.1, + mtl >= 2.2.1, old-time >= 1.1, presburger >= 1.1, pretty >= 1.1, @@ -119,6 +120,7 @@ library Data.SBV, Data.SBV.Bridge.Boolector, Data.SBV.Bridge.CVC4, + Data.SBV.Bridge.MathSAT, Data.SBV.Bridge.Yices, Data.SBV.Bridge.Z3, Data.SBV.Internals, @@ -141,6 +143,7 @@ library Data.SBV.Provers.CVC4, Data.SBV.Provers.Yices, Data.SBV.Provers.Z3, + Data.SBV.Provers.MathSAT, Data.SBV.Tools.ExpectedValue, Data.SBV.Tools.GenTest, Data.SBV.Tools.Optimize, diff --git a/sbv/Data/SBV.hs b/sbv/Data/SBV.hs index 30844aba..5b71291c 100644 --- a/sbv/Data/SBV.hs +++ b/sbv/Data/SBV.hs @@ -40,7 +40,7 @@ -- -- * 'SInt8', 'SInt16', 'SInt32', 'SInt64': Symbolic Ints (signed). -- --- * 'SArray', 'SFunArray': Flat arrays of symbolic values. +-- * 'SInteger': Unbounded signed integers. -- -- * 'SReal': Algebraic-real numbers -- @@ -48,6 +48,8 @@ -- -- * 'SDouble': IEEE-754 double-precision floating point values -- +-- * 'SArray', 'SFunArray': Flat arrays of symbolic values. +-- -- * Symbolic polynomials over GF(2^n), polynomial arithmetic, and CRCs. -- -- * Uninterpreted constants and functions over symbolic values, with user @@ -92,10 +94,18 @@ -- -- * Boolector from Johannes Kepler University: -- +-- * MathSAT from Fondazione Bruno Kessler and DISI-University of Trento: +-- +-- SBV also allows calling these solvers in parallel, either getting results from multiple solvers +-- or returning the fastest one. (See 'proveWithAll', 'proveWithAny', etc.) +-- -- Support for other compliant solvers can be added relatively easily, please -- get in touch if there is a solver you'd like to see included. --------------------------------------------------------------------------------- +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverlappingInstances #-} + module Data.SBV ( -- * Programming with symbolic values -- $progIntro @@ -113,7 +123,7 @@ module Data.SBV ( , SInteger -- *** IEEE-floating point numbers -- $floatingPoints - , SFloat, SDouble, RoundingMode(..), nan, infinity, sNaN, sInfinity, fusedMA + , SFloat, SDouble, RoundingMode(..), nan, infinity, sNaN, sInfinity, fusedMA, isSNaN, isFPPoint -- *** Signed algebraic reals -- $algReals , SReal, AlgReal, toSReal @@ -187,6 +197,10 @@ module Data.SBV ( -- ** Checking constraint vacuity , isVacuous, isVacuousWith + -- * Proving properties using multiple solvers + -- $multiIntro + , proveWithAll, proveWithAny, satWithAll, satWithAny, allSatWithAll, allSatWithAny + -- * Optimization -- $optimizeIntro , minimize, maximize, optimize @@ -205,9 +219,10 @@ module Data.SBV ( -- ** Programmable model extraction -- $programmableExtraction , SatModel(..), Modelable(..), displayModels, extractModels + , getModelDictionaries, getModelValues, getModelUninterpretedValues -- * SMT Interface: Configurations and solvers - , SMTConfig(..), OptimizeOpts(..), SMTSolver(..), boolector, cvc4, yices, z3, sbvCurrentSolver, defaultSMTCfg, sbvCheckSolverInstallation + , SMTConfig(..), SMTLibLogic(..), Logic(..), OptimizeOpts(..), Solver(..), SMTSolver(..), boolector, cvc4, yices, z3, mathSAT, sbvCurrentSolver, defaultSMTCfg, sbvCheckSolverInstallation, sbvAvailableSolvers -- * Symbolic computations , Symbolic, output, SymWord(..) @@ -253,6 +268,10 @@ module Data.SBV ( , module Data.Ratio ) where +import Control.Monad (filterM) +import Control.Concurrent.Async (async, waitAny, waitAnyCancel) +import System.IO.Unsafe (unsafeInterleaveIO) -- only used safely! + import Data.SBV.BitVectors.AlgReals import Data.SBV.BitVectors.Data import Data.SBV.BitVectors.Model @@ -280,6 +299,140 @@ import Data.Word sbvCurrentSolver :: SMTConfig sbvCurrentSolver = z3 +-- | Note that the floating point value NaN does not compare equal to itself, +-- so we need a special recognizer for that. Haskell provides the isNaN predicate +-- with the `RealFrac` class, which unfortunately is not currently implementable for +-- symbolic cases. (Requires trigonometric functions etc.) Thus, we provide this +-- recognizer separately. Note that the definition simply tests equality against +-- itself, which fails for NaN. Who said equality for floating point was reflexive? +isSNaN :: (Floating a, SymWord a) => SBV a -> SBool +isSNaN x = x ./= x + +-- | We call a FP number FPPoint if it is neither NaN, nor +/- infinity. +isFPPoint :: (Floating a, SymWord a) => SBV a -> SBool +isFPPoint x = x .== x -- gets rid of NaN's + &&& x .< sInfinity -- gets rid of +inf + &&& x .> -sInfinity -- gets rid of -inf + +-- | Form the symbolic conjunction of a given list of boolean conditions. Useful in expressing +-- problems with constraints, like the following: +-- +-- @ +-- do [x, y, z] <- sIntegers [\"x\", \"y\", \"z\"] +-- solve [x .> 5, y + z .< x] +-- @ +solve :: [SBool] -> Symbolic SBool +solve = return . bAnd + +-- | Check whether the given solver is installed and is ready to go. This call does a +-- simple call to the solver to ensure all is well. +sbvCheckSolverInstallation :: SMTConfig -> IO Bool +sbvCheckSolverInstallation cfg = do ThmResult r <- proveWith cfg $ \x -> (x+x) .== ((x*2) :: SWord8) + case r of + Unsatisfiable _ -> return True + _ -> return False + +-- The default configs +defaultSolverConfig :: Solver -> SMTConfig +defaultSolverConfig Z3 = z3 +defaultSolverConfig Yices = yices +defaultSolverConfig Boolector = boolector +defaultSolverConfig CVC4 = cvc4 +defaultSolverConfig MathSAT = mathSAT + +-- | Return the known available solver configs, installed on your machine. +sbvAvailableSolvers :: IO [SMTConfig] +sbvAvailableSolvers = filterM sbvCheckSolverInstallation (map defaultSolverConfig [minBound .. maxBound]) + +sbvWithAny :: Provable a => [SMTConfig] -> (SMTConfig -> a -> IO b) -> a -> IO (Solver, b) +sbvWithAny [] _ _ = error "SBV.withAny: No solvers given!" +sbvWithAny solvers what a = snd `fmap` (mapM try solvers >>= waitAnyCancel) + where try s = async $ what s a >>= \r -> return (name (solver s), r) + +sbvWithAll :: Provable a => [SMTConfig] -> (SMTConfig -> a -> IO b) -> a -> IO [(Solver, b)] +sbvWithAll solvers what a = mapM try solvers >>= (unsafeInterleaveIO . go) + where try s = async $ what s a >>= \r -> return (name (solver s), r) + go [] = return [] + go as = do (d, r) <- waitAny as + rs <- unsafeInterleaveIO $ go (filter (/= d) as) + return (r : rs) + +-- | Prove a property with multiple solvers, running them in separate threads. The +-- results will be returned in the order produced. +proveWithAll :: Provable a => [SMTConfig] -> a -> IO [(Solver, ThmResult)] +proveWithAll = (`sbvWithAll` proveWith) + +-- | Prove a property with multiple solvers, running them in separate threads. Only +-- the result of the first one to finish will be returned, remaining threads will be killed. +proveWithAny :: Provable a => [SMTConfig] -> a -> IO (Solver, ThmResult) +proveWithAny = (`sbvWithAny` proveWith) + +-- | Find a satisfying assignment to a property with multiple solvers, running them in separate threads. The +-- results will be returned in the order produced. +satWithAll :: Provable a => [SMTConfig] -> a -> IO [(Solver, SatResult)] +satWithAll = (`sbvWithAll` satWith) + +-- | Find a satisfying assignment to a property with multiple solvers, running them in separate threads. Only +-- the result of the first one to finish will be returned, remaining threads will be killed. +satWithAny :: Provable a => [SMTConfig] -> a -> IO (Solver, SatResult) +satWithAny = (`sbvWithAny` satWith) + +-- | Find all satisfying assignments to a property with multiple solvers, running them in separate threads. Only +-- the result of the first one to finish will be returned, remaining threads will be killed. +allSatWithAll :: Provable a => [SMTConfig] -> a -> IO [(Solver, AllSatResult)] +allSatWithAll = (`sbvWithAll` allSatWith) + +-- | Find all satisfying assignments to a property with multiple solvers, running them in separate threads. Only +-- the result of the first one to finish will be returned, remaining threads will be killed. +allSatWithAny :: Provable a => [SMTConfig] -> a -> IO (Solver, AllSatResult) +allSatWithAny = (`sbvWithAny` allSatWith) + +-- | Equality as a proof method. Allows for +-- very concise construction of equivalence proofs, which is very typical in +-- bit-precise proofs. +infix 4 === +class Equality a where + (===) :: a -> a -> IO ThmResult + +instance (SymWord a, EqSymbolic z) => Equality (SBV a -> z) where + k === l = prove $ \a -> k a .== l a + +instance (SymWord a, SymWord b, EqSymbolic z) => Equality (SBV a -> SBV b -> z) where + k === l = prove $ \a b -> k a b .== l a b + +instance (SymWord a, SymWord b, EqSymbolic z) => Equality ((SBV a, SBV b) -> z) where + k === l = prove $ \a b -> k (a, b) .== l (a, b) + +instance (SymWord a, SymWord b, SymWord c, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> z) where + k === l = prove $ \a b c -> k a b c .== l a b c + +instance (SymWord a, SymWord b, SymWord c, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c) -> z) where + k === l = prove $ \a b c -> k (a, b, c) .== l (a, b, c) + +instance (SymWord a, SymWord b, SymWord c, SymWord d, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> z) where + k === l = prove $ \a b c d -> k a b c d .== l a b c d + +instance (SymWord a, SymWord b, SymWord c, SymWord d, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d) -> z) where + k === l = prove $ \a b c d -> k (a, b, c, d) .== l (a, b, c, d) + +instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> z) where + k === l = prove $ \a b c d e -> k a b c d e .== l a b c d e + +instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d, SBV e) -> z) where + k === l = prove $ \a b c d e -> k (a, b, c, d, e) .== l (a, b, c, d, e) + +instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> SBV f -> z) where + k === l = prove $ \a b c d e f -> k a b c d e f .== l a b c d e f + +instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d, SBV e, SBV f) -> z) where + k === l = prove $ \a b c d e f -> k (a, b, c, d, e, f) .== l (a, b, c, d, e, f) + +instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, SymWord g, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> SBV f -> SBV g -> z) where + k === l = prove $ \a b c d e f g -> k a b c d e f g .== l a b c d e f g + +instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, SymWord g, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d, SBV e, SBV f, SBV g) -> z) where + k === l = prove $ \a b c d e f g -> k (a, b, c, d, e, f, g) .== l (a, b, c, d, e, f, g) + -- Haddock section documentation {- $progIntro The SBV library is really two things: @@ -309,12 +462,32 @@ design goal is to let SMT solvers be used without any knowledge of how SMT solve or how different logics operate. The details are hidden behind the SBV framework, providing Haskell programmers with a clean API that is unencumbered by the details of individual solvers. To that end, we use the SMT-Lib standard () -to communicate with arbitrary SMT solvers. Unfortunately, -the SMT-Lib version 1.X does not standardize how models are communicated back from solvers, so -there is some work in parsing individual SMT solver output. The 2.X version of the SMT-Lib -standard (not yet implemented by SMT solvers widely, unfortunately) will bring new standard features -for getting models; at which time the SBV framework can be modified into a truly plug-and-play -system where arbitrary SMT solvers can be used. +to communicate with arbitrary SMT solvers. +-} + +{- $multiIntro +On a multi-core machine, it might be desirable to try a given property using multiple SMT solvers, +using parallel threads. Even with machines with single-cores, threading can be helpful if you +want to try out multiple-solvers but do not know which one would work the best +for the problem at hand ahead of time. + +The functions in this section allow proving/satisfiability-checking with multiple +backends at the same time. Each function comes in two variants, one that +returns the results from all solvers, the other that returns the fastest one. + +The @All@ variants, (i.e., 'proveWithAll', 'satWithAll', 'allSatWithAll') run all solvers and +return all the results. SBV internally makes sure that the result is lazily generated; so, +the order of solvers given does not matter. In other words, the order of results will follow +the order of the solvers as they finish, not as given by the user. These variants are useful when you +want to make sure multiple-solvers agree (or disagree!) on a given problem. + +The @Any@ variants, (i.e., 'proveWithAny', 'satWithAny', 'allSatWithAny') will run all the solvers +in parallel, and return the results of the first one finishing. The other threads will then be killed. These variants +are useful when you do not care if the solvers produce the same result, but rather want to get the +solution as quickly as possible, taking advantage of modern many-core machines. + +Note that the function 'sbvAvailableSolvers' will return all the installed solvers, which can be +used as the first argument to all these functions, if you simply want to try all available solvers on a machine. -} {- $optimizeIntro diff --git a/sbv/Data/SBV/BitVectors/AlgReals.hs b/sbv/Data/SBV/BitVectors/AlgReals.hs index 31de4a3c..9fe08f2c 100644 --- a/sbv/Data/SBV/BitVectors/AlgReals.hs +++ b/sbv/Data/SBV/BitVectors/AlgReals.hs @@ -49,7 +49,7 @@ mkPolyReal (Left (exact, str)) mkPolyReal (Right (k, coeffs)) = AlgPolyRoot (k, Polynomial (normalize coeffs)) Nothing where normalize :: [(Integer, Integer)] -> [(Integer, Integer)] - normalize = merge . reverse . sortBy (compare `on` snd) + normalize = merge . sortBy (flip compare `on` snd) merge [] = [] merge [x] = [x] merge ((a, b):r@((c, d):xs)) diff --git a/sbv/Data/SBV/BitVectors/Data.hs b/sbv/Data/SBV/BitVectors/Data.hs index 2939167b..a225a5a8 100644 --- a/sbv/Data/SBV/BitVectors/Data.hs +++ b/sbv/Data/SBV/BitVectors/Data.hs @@ -17,31 +17,35 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE DefaultSignatures #-} +{-# LANGUAGE NamedFieldPuns #-} module Data.SBV.BitVectors.Data ( SBool, SWord8, SWord16, SWord32, SWord64 , SInt8, SInt16, SInt32, SInt64, SInteger, SReal, SFloat, SDouble , nan, infinity, sNaN, sInfinity, RoundingMode(..), smtLibSquareRoot, smtLibFusedMA , SymWord(..) - , CW(..), CWVal(..), cwSameType, cwIsBit, cwToBool, normCW + , CW(..), CWVal(..), AlgReal(..), cwSameType, cwIsBit, cwToBool , mkConstCW ,liftCW2, mapCW, mapCW2 - , SW(..), trueSW, falseSW, trueCW, falseCW + , SW(..), trueSW, falseSW, trueCW, falseCW, normCW , SBV(..), NodeId(..), mkSymSBV, mkSymSBVWithRandom , ArrayContext(..), ArrayInfo, SymArray(..), SFunArray(..), mkSFunArray, SArray(..), arrayUIKind - , sbvToSW, sbvToSymSW + , sbvToSW, sbvToSymSW, forceSWArg , SBVExpr(..), newExpr , cache, Cached, uncache, uncacheAI, HasKind(..) - , Op(..), NamedSymVar, UnintKind(..), getTableIndex, SBVPgm(..), Symbolic, runSymbolic, runSymbolic', State, inProofMode, SBVRunMode(..), Kind(..), Outputtable(..), Result(..) - , readResult, getResult + , Op(..), NamedSymVar, UnintKind(..), getTableIndex, SBVPgm(..), Symbolic, runSymbolic, runSymbolic', State, getPathCondition, extendPathCondition + , inProofMode, SBVRunMode(..), Kind(..), Outputtable(..), Result(..) + , Logic(..), SMTLibLogic(..) , getTraceInfo, getConstraints, addConstraint , SBVType(..), newUninterpreted, unintFnUIKind, addAxiom , Quantifier(..), needsExistentials , SMTLibPgm(..), SMTLibVersion(..) , SolverCapabilities(..) + , extractSymbolicSimulationState, getResult + , SMTScript(..), Solver(..), SMTSolver(..), SMTResult(..), SMTModel(..), SMTConfig(..), getSBranchRunConfig ) where -import Control.Applicative (Applicative) import Control.DeepSeq (NFData(..)) +import Control.Applicative (Applicative) import Control.Monad (when) import Control.Monad.Fix (MonadFix) import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT) @@ -60,6 +64,7 @@ import qualified Data.Set as Set (Set, empty, toList, insert) import qualified Data.Foldable as F (toList) import qualified Data.Sequence as S (Seq, empty, (|>)) +import System.Exit (ExitCode(..)) import System.Mem.StableName import System.Random @@ -130,8 +135,8 @@ cwSameType x y = cwKind x == cwKind y -- | Is this a bit? cwIsBit :: CW -> Bool cwIsBit x = case cwKind x of - KBounded False 1 -> True - _ -> False + KBool -> True + _ -> False -- | Convert a CW to a Haskell boolean (NB. Assumes input is well-kinded) cwToBool :: CW -> Bool @@ -152,7 +157,8 @@ normCW c@(CW (KBounded signed sz) (CWInteger v)) = c { cwVal = CWInteger norm } normCW c = c -- | Kind of symbolic value -data Kind = KBounded Bool Int +data Kind = KBool + | KBounded Bool Int | KUnbounded | KReal | KUninterpreted String @@ -161,7 +167,7 @@ data Kind = KBounded Bool Int deriving (Eq, Ord) instance Show Kind where - show (KBounded False 1) = "SBool" + show KBool = "SBool" show (KBounded False n) = "SWord" ++ show n show (KBounded True n) = "SInt" ++ show n show KUnbounded = "SInteger" @@ -176,6 +182,12 @@ newtype NodeId = NodeId Int deriving (Eq, Ord) -- | A symbolic word, tracking it's signedness and size. data SW = SW Kind NodeId deriving (Eq, Ord) +-- | Forcing an argument; this is a necessary evil to make sure all the arguments +-- to an uninterpreted function and sBranch test conditions are evaluated before called; +-- the semantics of uinterpreted functions is necessarily strict; deviating from Haskell's +forceSWArg :: SW -> IO () +forceSWArg (SW k n) = k `seq` n `seq` return () + -- | Quantifiers: forall or exists. Note that we allow -- arbitrary nestings. data Quantifier = ALL | EX deriving Eq @@ -186,19 +198,19 @@ needsExistentials = (EX `elem`) -- | Constant False as a SW. Note that this value always occupies slot -2. falseSW :: SW -falseSW = SW (KBounded False 1) $ NodeId (-2) +falseSW = SW KBool $ NodeId (-2) -- | Constant False as a SW. Note that this value always occupies slot -1. trueSW :: SW -trueSW = SW (KBounded False 1) $ NodeId (-1) +trueSW = SW KBool $ NodeId (-1) -- | Constant False as a CW. We represent it using the integer value 0. falseCW :: CW -falseCW = CW (KBounded False 1) (CWInteger 0) +falseCW = CW KBool (CWInteger 0) -- | Constant True as a CW. We represent it using the integer value 1. trueCW :: CW -trueCW = CW (KBounded False 1) (CWInteger 1) +trueCW = CW KBool (CWInteger 1) -- | A simple type for SBV computations, used mainly for uninterpreted constants. -- We keep track of the signedness/size of the arguments. A non-function will @@ -265,6 +277,7 @@ class HasKind a where showType :: a -> String -- defaults hasSign x = case kindOf x of + KBool -> False KBounded b _ -> b KUnbounded -> True KReal -> True @@ -272,13 +285,14 @@ class HasKind a where KDouble -> True KUninterpreted{} -> False intSizeOf x = case kindOf x of + KBool -> error "SBV.HasKind.intSizeOf((S)Bool)" KBounded _ s -> s KUnbounded -> error "SBV.HasKind.intSizeOf((S)Integer)" KReal -> error "SBV.HasKind.intSizeOf((S)Real)" KFloat -> error "SBV.HasKind.intSizeOf((S)Float)" KDouble -> error "SBV.HasKind.intSizeOf((S)Double)" KUninterpreted s -> error $ "SBV.HasKind.intSizeOf: Uninterpreted sort: " ++ s - isBoolean x | KBounded False 1 <- kindOf x = True + isBoolean x | KBool{} <- kindOf x = True | True = False isBounded x | KBounded{} <- kindOf x = True | True = False @@ -298,7 +312,7 @@ class HasKind a where default kindOf :: Data a => a -> Kind kindOf = KUninterpreted . tyconUQname . dataTypeName . dataTypeOf -instance HasKind Bool where kindOf _ = KBounded False 1 +instance HasKind Bool where kindOf _ = KBool instance HasKind Int8 where kindOf _ = KBounded True 8 instance HasKind Word8 where kindOf _ = KBounded False 8 instance HasKind Int16 where kindOf _ = KBounded True 16 @@ -421,7 +435,7 @@ data UnintKind = UFun Int String | UArr Int String -- in each case, arity a -- | Result of running a symbolic computation data Result = Result (Set.Set Kind) -- kinds used in the program [(String, CW)] -- quick-check counter-example information (if any) - [(String, [String])] -- uninterpreted code segments + [(String, [String])] -- uninterpeted code segments [(Quantifier, NamedSymVar)] -- inputs (possibly existential) [(SW, CW)] -- constants [((Int, Kind, Kind), [SW])] -- tables (automatically constructed) (tableno, index-type, result-type) elts @@ -540,9 +554,9 @@ arrayUIKind (i, (nm, _, ctx)) external (ArrayMerge{}) = False -- | Different means of running a symbolic piece of code -data SBVRunMode = Proof Bool -- ^ Symbolic simulation mode, for proof purposes. Bool is True if it's a sat instance - | CodeGen -- ^ Code generation mode - | Concrete StdGen -- ^ Concrete simulation mode. The StdGen is for the pConstrain acceptance in cross runs +data SBVRunMode = Proof (Bool, Maybe SMTConfig) -- ^ Symbolic simulation mode, for proof purposes. Bool is True if it's a sat instance. SMTConfig is used for 'sBranch' calls. + | CodeGen -- ^ Code generation mode + | Concrete StdGen -- ^ Concrete simulation mode. The StdGen is for the pConstrain acceptance in cross runs -- | Is this a concrete run? (i.e., quick-check or test-generation like) isConcreteMode :: SBVRunMode -> Bool @@ -552,6 +566,7 @@ isConcreteMode CodeGen = False -- | The state of the symbolic interpreter data State = State { runMode :: SBVRunMode + , pathCond :: SBool , rStdGen :: IORef StdGen , rCInfo :: IORef [(String, CW)] , rctr :: IORef Int @@ -571,6 +586,14 @@ data State = State { runMode :: SBVRunMode , rAICache :: IORef (Cache Int) } +-- | Get the current path condition +getPathCondition :: State -> SBool +getPathCondition = pathCond + +-- | Extend the path condition with the given test value. +extendPathCondition :: State -> (SBool -> SBool) -> State +extendPathCondition st f = st{pathCond = f (pathCond st)} + -- | Are we running in proof mode? inProofMode :: State -> Bool inProofMode s = case runMode s of @@ -578,6 +601,12 @@ inProofMode s = case runMode s of CodeGen -> False Concrete{} -> False +-- | If in proof mode, get the underlying configuration (used for 'sBranch') +getSBranchRunConfig :: State -> Maybe SMTConfig +getSBranchRunConfig st = case runMode st of + Proof (_, s) -> s + _ -> Nothing + -- | The "Symbolic" value. Either a constant (@Left@) or a symbolic -- value (@Right Cached@). Note that caching is essential for making -- sure sharing is preserved. The parameter 'a' is phantom, but is @@ -636,7 +665,7 @@ infinity = 1/0 -- | Symbolic variant of Not-A-Number. This value will inhabit both -- 'SDouble' and 'SFloat'. sNaN :: (Floating a, SymWord a) => SBV a -sNaN = literal nan +sNaN = literal nan -- | Symbolic variant of infinity. This value will inhabit both -- 'SDouble' and 'SFloat'. @@ -744,6 +773,7 @@ getTableIndex st at rt elts = do -- | Create a constant word from an integral mkConstCW :: Integral a => Kind -> a -> CW +mkConstCW KBool a = normCW $ CW KBool (CWInteger (toInteger a)) mkConstCW k@(KBounded{}) a = normCW $ CW k (CWInteger (toInteger a)) mkConstCW KUnbounded a = normCW $ CW KUnbounded (CWInteger (toInteger a)) mkConstCW KReal a = normCW $ CW KReal (CWAlgReal (fromInteger (toInteger a))) @@ -783,21 +813,20 @@ newtype Symbolic a = Symbolic (ReaderT State IO a) mkSymSBV :: forall a. (Random a, SymWord a) => Maybe Quantifier -> Kind -> Maybe String -> Symbolic (SBV a) mkSymSBV = mkSymSBVWithRandom randomIO -mkSymSBVWithRandom :: forall a. SymWord a => - IO (SBV a) -> Maybe Quantifier -> Kind -> Maybe String -> Symbolic (SBV a) -mkSymSBVWithRandom random mbQ k mbNm = do +mkSymSBVWithRandom :: forall a. SymWord a => IO (SBV a) -> Maybe Quantifier -> Kind -> Maybe String -> Symbolic (SBV a) +mkSymSBVWithRandom rand mbQ k mbNm = do st <- ask let q = case (mbQ, runMode st) of - (Just x, _) -> x -- user given, just take it - (Nothing, Concrete{}) -> ALL -- concrete simulation, pick universal - (Nothing, Proof True) -> EX -- sat mode, pick existential - (Nothing, Proof False) -> ALL -- proof mode, pick universal - (Nothing, CodeGen) -> ALL -- code generation, pick universal + (Just x, _) -> x -- user given, just take it + (Nothing, Concrete{}) -> ALL -- concrete simulation, pick universal + (Nothing, Proof (True, _)) -> EX -- sat mode, pick existential + (Nothing, Proof (False, _)) -> ALL -- proof mode, pick universal + (Nothing, CodeGen) -> ALL -- code generation, pick universal case runMode st of Concrete _ | q == EX -> case mbNm of Nothing -> error $ "Cannot quick-check in the presence of existential variables, type: " ++ showType (undefined :: SBV a) Just nm -> error $ "Cannot quick-check in the presence of existential variable " ++ nm ++ " :: " ++ showType (undefined :: SBV a) - Concrete _ -> do v@(SBV _ (Left cw)) <- liftIO random + Concrete _ -> do v@(SBV _ (Left cw)) <- liftIO rand liftIO $ modifyIORef (rCInfo st) ((maybe "_" id mbNm, cw):) return v _ -> do (sw, internalName) <- liftIO $ newSW st k @@ -868,7 +897,7 @@ addAxiom nm ax = do -- | Run a symbolic computation in Proof mode and return a 'Result'. The boolean -- argument indicates if this is a sat instance or not. -runSymbolic :: Bool -> Symbolic a -> IO Result +runSymbolic :: (Bool, Maybe SMTConfig) -> Symbolic a -> IO Result runSymbolic b c = snd `fmap` runSymbolic' (Proof b) c -- | Run a symbolic computation, and return a extra value paired up with the 'Result' @@ -894,6 +923,7 @@ runSymbolic' currentRunMode (Symbolic c) = do Concrete g -> newIORef g _ -> newStdGen >>= newIORef let st = State { runMode = currentRunMode + , pathCond = SBV KBool (Left trueCW) , rStdGen = rGen , rCInfo = cInfo , rctr = ctr @@ -912,34 +942,37 @@ runSymbolic' currentRunMode (Symbolic c) = do , rAICache = aiCache , rConstraints = cstrs } - _ <- newConst st (mkConstCW (KBounded False 1) (0::Integer)) -- s(-2) == falseSW - _ <- newConst st (mkConstCW (KBounded False 1) (1::Integer)) -- s(-1) == trueSW + _ <- newConst st falseCW -- s(-2) == falseSW + _ <- newConst st trueCW -- s(-1) == trueSW r <- runReaderT c st - res <- readResult st - return $ (r, res) + res <- extractSymbolicSimulationState st + return (r, res) -readResult :: State -> IO Result -readResult st = do - rpgm <- readIORef (spgm st) - inpsO <- reverse `fmap` readIORef (rinps st) - outsO <- reverse `fmap` readIORef (routs st) - let swap (a, b) = (b, a) - cmp (a, _) (b, _) = a `compare` b - cnsts <- (sortBy cmp . map swap . Map.toList) `fmap` readIORef (rconstMap st) - tbls <- (sortBy (\((x, _, _), _) ((y, _, _), _) -> x `compare` y) . map swap . Map.toList) `fmap` readIORef (rtblMap st) - arrs <- IMap.toAscList `fmap` readIORef (rArrayMap st) - unint <- Map.toList `fmap` readIORef (rUIMap st) - axs <- reverse `fmap` readIORef (raxioms st) - knds <- readIORef (rUsedKinds st) - cgMap <- Map.toList `fmap` readIORef (rCgMap st) - traceVals <- reverse `fmap` readIORef (rCInfo st) - extraCstrs <- reverse `fmap` readIORef (rConstraints st) - return $ Result knds traceVals cgMap inpsO cnsts tbls arrs unint axs rpgm extraCstrs outsO +-- | Grab the program from a running symbolic simulation state. This is useful for internal purposes, for +-- instance when implementing 'sBranch'. +extractSymbolicSimulationState :: State -> IO Result +extractSymbolicSimulationState st@State{ spgm=pgm, rinps=inps, routs=outs, rtblMap=tables, rArrayMap=arrays, rUIMap=uis, raxioms=axioms + , rUsedKinds=usedKinds, rCgMap=cgs, rCInfo=cInfo, rConstraints = cstrs} = do + SBVPgm rpgm <- readIORef pgm + inpsO <- reverse `fmap` readIORef inps + outsO <- reverse `fmap` readIORef outs + let swap (a, b) = (b, a) + cmp (a, _) (b, _) = a `compare` b + cnsts <- (sortBy cmp . map swap . Map.toList) `fmap` readIORef (rconstMap st) + tbls <- (sortBy (\((x, _, _), _) ((y, _, _), _) -> x `compare` y) . map swap . Map.toList) `fmap` readIORef tables + arrs <- IMap.toAscList `fmap` readIORef arrays + unint <- Map.toList `fmap` readIORef uis + axs <- reverse `fmap` readIORef axioms + knds <- readIORef usedKinds + cgMap <- Map.toList `fmap` readIORef cgs + traceVals <- reverse `fmap` readIORef cInfo + extraCstrs <- reverse `fmap` readIORef cstrs + return $ Result knds traceVals cgMap inpsO cnsts tbls arrs unint axs (SBVPgm rpgm) extraCstrs outsO getResult :: Symbolic Result getResult = do st <- ask - liftIO $ readResult st + liftIO $ extractSymbolicSimulationState st ------------------------------------------------------------------------------- -- * Symbolic Words @@ -1024,11 +1057,11 @@ class (HasKind a, Ord a) => SymWord a where let k = KUninterpreted sortName liftIO $ registerKind st k let q = case (mbQ, runMode st) of - (Just x, _) -> x - (Nothing, Proof True) -> EX - (Nothing, Proof False) -> ALL - (Nothing, Concrete{}) -> error $ "SBV: Uninterpreted sort " ++ sortName ++ " can not be used in concrete simulation mode." - (Nothing, CodeGen) -> error $ "SBV: Uninterpreted sort " ++ sortName ++ " can not be used in code-generation mode." + (Just x, _) -> x + (Nothing, Proof (True, _)) -> EX + (Nothing, Proof (False, _)) -> ALL + (Nothing, Concrete{}) -> error $ "SBV: Uninterpreted sort " ++ sortName ++ " can not be used in concrete simulation mode." + (Nothing, CodeGen) -> error $ "SBV: Uninterpreted sort " ++ sortName ++ " can not be used in code-generation mode." ctr <- liftIO $ incCtr st let sw = SW k (NodeId ctr) nm = maybe ('s':show ctr) id mbNm @@ -1264,6 +1297,55 @@ instance NFData a => NFData (SBV a) where rnf (SBV x y) = rnf x `seq` rnf y `seq` () instance NFData SBVPgm +instance NFData SMTResult where + rnf (Unsatisfiable _) = () + rnf (Satisfiable _ xs) = rnf xs `seq` () + rnf (Unknown _ xs) = rnf xs `seq` () + rnf (ProofError _ xs) = rnf xs `seq` () + rnf (TimeOut _) = () + +instance NFData SMTModel where + rnf (SMTModel assocs unints uarrs) = rnf assocs `seq` rnf unints `seq` rnf uarrs `seq` () + +-- | SMT-Lib logics. If left unspecified SBV will pick the logic based on what it determines is needed. However, the +-- user can override this choice using the 'useLogic' parameter to the configuration. This is especially handy if +-- one is experimenting with custom logics that might be supported on new solvers. +data SMTLibLogic + = AUFLIA -- ^ Formulas over the theory of linear integer arithmetic and arrays extended with free sort and function symbols but restricted to arrays with integer indices and values + | AUFLIRA -- ^ Linear formulas with free sort and function symbols over one- and two-dimentional arrays of integer index and real value + | AUFNIRA -- ^ Formulas with free function and predicate symbols over a theory of arrays of arrays of integer index and real value + | LRA -- ^ Linear formulas in linear real arithmetic + | UFLRA -- ^ Linear real arithmetic with uninterpreted sort and function symbols. + | UFNIA -- ^ Non-linear integer arithmetic with uninterpreted sort and function symbols. + | QF_ABV -- ^ Quantifier-free formulas over the theory of bitvectors and bitvector arrays + | QF_AUFBV -- ^ Quantifier-free formulas over the theory of bitvectors and bitvector arrays extended with free sort and function symbols + | QF_AUFLIA -- ^ Quantifier-free linear formulas over the theory of integer arrays extended with free sort and function symbols + | QF_AX -- ^ Quantifier-free formulas over the theory of arrays with extensionality + | QF_BV -- ^ Quantifier-free formulas over the theory of fixed-size bitvectors + | QF_IDL -- ^ Difference Logic over the integers. Boolean combinations of inequations of the form x - y < b where x and y are integer variables and b is an integer constant + | QF_LIA -- ^ Unquantified linear integer arithmetic. In essence, Boolean combinations of inequations between linear polynomials over integer variables + | QF_LRA -- ^ Unquantified linear real arithmetic. In essence, Boolean combinations of inequations between linear polynomials over real variables. + | QF_NIA -- ^ Quantifier-free integer arithmetic. + | QF_NRA -- ^ Quantifier-free real arithmetic. + | QF_RDL -- ^ Difference Logic over the reals. In essence, Boolean combinations of inequations of the form x - y < b where x and y are real variables and b is a rational constant. + | QF_UF -- ^ Unquantified formulas built over a signature of uninterpreted (i.e., free) sort and function symbols. + | QF_UFBV -- ^ Unquantified formulas over bitvectors with uninterpreted sort function and symbols. + | QF_UFIDL -- ^ Difference Logic over the integers (in essence) but with uninterpreted sort and function symbols. + | QF_UFLIA -- ^ Unquantified linear integer arithmetic with uninterpreted sort and function symbols. + | QF_UFLRA -- ^ Unquantified linear real arithmetic with uninterpreted sort and function symbols. + | QF_UFNRA -- ^ Unquantified non-linear real arithmetic with uninterpreted sort and function symbols. + | QF_FPABV -- ^ Quantifier-free formulas over the theory of floating point numbers, arrays, and bit-vectors + | QF_FPA -- ^ Quantifier-free formulas over the theory of floating point numbers + deriving Show + +-- | Chosen logic for the solver +data Logic = PredefinedLogic SMTLibLogic -- ^ Use one of the logics as defined by the standard + | CustomLogic String -- ^ Use this name for the logic + +instance Show Logic where + show (PredefinedLogic l) = show l + show (CustomLogic s) = s + -- | Translation tricks needed for specific capabilities afforded by each solver data SolverCapabilities = SolverCapabilities { capSolverName :: String -- ^ Name of the solver @@ -1277,3 +1359,84 @@ data SolverCapabilities = SolverCapabilities { , supportsFloats :: Bool -- ^ Does the solver support single-precision floating point numbers? , supportsDoubles :: Bool -- ^ Does the solver support double-precision floating point numbers? } + +-- | Solver configuration. See also 'z3', 'yices', 'cvc4', 'boolector', 'mathSAT', etc. which are instantiations of this type for those solvers, with +-- reasonable defaults. In particular, custom configuration can be created by varying those values. (Such as @z3{verbose=True}@.) +-- +-- Most fields are self explanatory. The notion of precision for printing algebraic reals stems from the fact that such values does +-- not necessarily have finite decimal representations, and hence we have to stop printing at some depth. It is important to +-- emphasize that such values always have infinite precision internally. The issue is merely with how we print such an infinite +-- precision value on the screen. The field 'printRealPrec' controls the printing precision, by specifying the number of digits after +-- the decimal point. The default value is 16, but it can be set to any positive integer. +-- +-- When printing, SBV will add the suffix @...@ at the and of a real-value, if the given bound is not sufficient to represent the real-value +-- exactly. Otherwise, the number will be written out in standard decimal notation. Note that SBV will always print the whole value if it +-- is precise (i.e., if it fits in a finite number of digits), regardless of the precision limit. The limit only applies if the representation +-- of the real value is not finite, i.e., if it is not rational. +data SMTConfig = SMTConfig { + verbose :: Bool -- ^ Debug mode + , timing :: Bool -- ^ Print timing information on how long different phases took (construction, solving, etc.) + , sBranchTimeOut :: Maybe Int -- ^ How much time to give to the solver for each call of 'sBranch' check. (In seconds. Default: No limit.) + , timeOut :: Maybe Int -- ^ How much time to give to the solver. (In seconds. Default: No limit.) + , printBase :: Int -- ^ Print integral literals in this base (2, 8, and 10, and 16 are supported.) + , printRealPrec :: Int -- ^ Print algebraic real values with this precision. (SReal, default: 16) + , solverTweaks :: [String] -- ^ Additional lines of script to give to the solver (user specified) + , satCmd :: String -- ^ Usually "(check-sat)". However, users might tweak it based on solver characteristics. + , smtFile :: Maybe FilePath -- ^ If Just, the generated SMT script will be put in this file (for debugging purposes mostly) + , useSMTLib2 :: Bool -- ^ If True, we'll treat the solver as using SMTLib2 input format. Otherwise, SMTLib1 + , solver :: SMTSolver -- ^ The actual SMT solver. + , roundingMode :: RoundingMode -- ^ Rounding mode to use for floating-point conversions + , useLogic :: Maybe Logic -- ^ If Nothing, pick automatically. Otherwise, either use the given one, or use the custom string. + } + +instance Show SMTConfig where + show = show . solver + +-- | A model, as returned by a solver +data SMTModel = SMTModel { + modelAssocs :: [(String, CW)] -- ^ Mapping of symbolic values to constants. + , modelArrays :: [(String, [String])] -- ^ Arrays, very crude; only works with Yices. + , modelUninterps :: [(String, [String])] -- ^ Uninterpreted funcs; very crude; only works with Yices. + } + deriving Show + +-- | The result of an SMT solver call. Each constructor is tagged with +-- the 'SMTConfig' that created it so that further tools can inspect it +-- and build layers of results, if needed. For ordinary uses of the library, +-- this type should not be needed, instead use the accessor functions on +-- it. (Custom Show instances and model extractors.) +data SMTResult = Unsatisfiable SMTConfig -- ^ Unsatisfiable + | Satisfiable SMTConfig SMTModel -- ^ Satisfiable with model + | Unknown SMTConfig SMTModel -- ^ Prover returned unknown, with a potential (possibly bogus) model + | ProofError SMTConfig [String] -- ^ Prover errored out + | TimeOut SMTConfig -- ^ Computation timed out (see the 'timeout' combinator) + +-- | A script, to be passed to the solver. +data SMTScript = SMTScript { + scriptBody :: String -- ^ Initial feed + , scriptModel :: Maybe String -- ^ Optional continuation script, if the result is sat + } + +-- | An SMT engine +type SMTEngine = SMTConfig -> Bool -> [(Quantifier, NamedSymVar)] -> [(String, UnintKind)] -> [Either SW (SW, [SW])] -> String -> IO SMTResult + +-- | Solvers that SBV is aware of +data Solver = Z3 + | Yices + | Boolector + | CVC4 + | MathSAT + deriving (Show, Enum, Bounded) + +-- | An SMT solver +data SMTSolver = SMTSolver { + name :: Solver -- ^ The solver in use + , executable :: String -- ^ The path to its executable + , options :: [String] -- ^ Options to provide to the solver + , engine :: SMTEngine -- ^ The solver engine, responsible for interpreting solver output + , xformExitCode :: ExitCode -> ExitCode -- ^ Should we re-interpret exit codes. Most solvers behave rationally, i.e., id will do. Some (like CVC4) don't. + , capabilities :: SolverCapabilities -- ^ Various capabilities of the solver + } + +instance Show SMTSolver where + show = show . name diff --git a/sbv/Data/SBV/BitVectors/Model.hs b/sbv/Data/SBV/BitVectors/Model.hs index 24c8741b..6b8c8314 100644 --- a/sbv/Data/SBV/BitVectors/Model.hs +++ b/sbv/Data/SBV/BitVectors/Model.hs @@ -52,6 +52,23 @@ import Data.SBV.BitVectors.AlgReals import Data.SBV.BitVectors.Data import Data.SBV.Utils.Boolean +import Data.SBV.Provers.Prover (isSBranchFeasibleInState) + +-- The following two imports are only needed because of the doctest expressions we have. Sigh.. +-- It might be a good idea to reorg some of the content to avoid this. +import Data.SBV.Provers.Prover (isVacuous, prove) +import Data.SBV.SMT.SMT (ThmResult) + +-- | Newer versions of GHC (Starting with 7.8 I think), distinguishes between FiniteBits and Bits classes. +-- We should really use FiniteBitSize for SBV which would make things better. In the interim, just work +-- around pesky warnings.. +ghcBitSize :: Bits a => a -> Int +#if __GLASGOW_HASKELL__ >= 708 +ghcBitSize x = maybe (error "SBV.ghcBitSize: Unexpected non-finite usage!") id (bitSizeMaybe x) +#else +ghcBitSize = bitSize +#endif + noUnint :: String -> a noUnint x = error $ "Unexpected operation called on uninterpreted value: " ++ show x @@ -76,20 +93,20 @@ liftSym2 opS _ _ _ _ _ a@(SBV k _) b liftSym2B :: (State -> Kind -> SW -> SW -> IO SW) -> (CW -> CW -> Bool) -> (AlgReal -> AlgReal -> Bool) -> (Integer -> Integer -> Bool) -> (Float -> Float -> Bool) -> (Double -> Double -> Bool) -> SBV b -> SBV b -> SBool liftSym2B _ okCW opCR opCI opCF opCD (SBV _ (Left a)) (SBV _ (Left b)) | okCW a b = literal (liftCW2 opCR opCI opCF opCD noUnint2 a b) -liftSym2B opS _ _ _ _ _ a b = SBV (KBounded False 1) $ Right $ liftSW2 opS (KBounded False 1) a b +liftSym2B opS _ _ _ _ _ a b = SBV KBool $ Right $ liftSW2 opS KBool a b liftSym1Bool :: (State -> Kind -> SW -> IO SW) -> (Bool -> Bool) -> SBool -> SBool liftSym1Bool _ opC (SBV _ (Left a)) = literal $ opC $ cwToBool a -liftSym1Bool opS _ a = SBV (KBounded False 1) $ Right $ cache c +liftSym1Bool opS _ a = SBV KBool $ Right $ cache c where c st = do sw <- sbvToSW st a - opS st (KBounded False 1) sw + opS st KBool sw liftSym2Bool :: (State -> Kind -> SW -> SW -> IO SW) -> (Bool -> Bool -> Bool) -> SBool -> SBool -> SBool liftSym2Bool _ opC (SBV _ (Left a)) (SBV _ (Left b)) = literal (cwToBool a `opC` cwToBool b) -liftSym2Bool opS _ a b = SBV (KBounded False 1) $ Right $ cache c +liftSym2Bool opS _ a b = SBV KBool $ Right $ cache c where c st = do sw1 <- sbvToSW st a sw2 <- sbvToSW st b - opS st (KBounded False 1) sw1 sw2 + opS st KBool sw1 sw2 mkSymOpSC :: (SW -> SW -> Maybe SW) -> Op -> State -> Kind -> SW -> SW -> IO SW mkSymOpSC shortCut op st k a b = maybe (newExpr st k (SBVApp op [a, b])) return (shortCut a b) @@ -128,8 +145,8 @@ genMkSymVar k mbq Nothing = genVar_ mbq k genMkSymVar k mbq (Just s) = genVar mbq k s instance SymWord Bool where - mkSymWord = genMkSymVar (KBounded False 1) - literal x = genLiteral (KBounded False 1) (if x then (1::Integer) else 0) + mkSymWord = genMkSymVar KBool + literal x = genLiteral KBool (if x then (1::Integer) else 0) fromCW = cwToBool mbMaxBound = Just maxBound mbMinBound = Just minBound @@ -755,10 +772,12 @@ instance ({-Num a,-} Bits a, SymWord a) => Bits (SBV a) where | True = liftSym2 (mkSymOp XOr) (const (const True)) (noReal "xor") xor (noFloat "xor") (noDouble "xor") x y complement = liftSym1 (mkSymOp1 Not) (noRealUnary "complement") complement (noFloatUnary "complement") (noDoubleUnary "complement") bitSize x = case kindOf x of KBounded _ w -> w +#if __GLASGOW_HASKELL__ >= 708 + bitSizeMaybe _ = Just $ intSizeOf (undefined :: a) +#endif isSigned x = case kindOf x of KBounded s _ -> s bit i = 1 `shiftL` i setBit x i = x .|. sbvFromInteger (kindOf x) (bit i) - --BH TODO: implement setBit, clearBit, etc. separately. shiftL x y | y < 0 = shiftR x (-y) | y == 0 = x @@ -770,12 +789,12 @@ instance ({-Num a,-} Bits a, SymWord a) => Bits (SBV a) where rotateL x y | y < 0 = rotateR x (-y) | y == 0 = x - | isBounded x = let sz = bitSize x in liftSym1 (mkSymOp1 (Rol (y `mod` sz))) (noRealUnary "rotateL") (rot True sz y) (noFloatUnary "rotateL") (noDoubleUnary "rotateL") x + | isBounded x = let sz = ghcBitSize x in liftSym1 (mkSymOp1 (Rol (y `mod` sz))) (noRealUnary "rotateL") (rot True sz y) (noFloatUnary "rotateL") (noDoubleUnary "rotateL") x | True = shiftL x y -- for unbounded Integers, rotateL is the same as shiftL in Haskell rotateR x y | y < 0 = rotateL x (-y) | y == 0 = x - | isBounded x = let sz = bitSize x in liftSym1 (mkSymOp1 (Ror (y `mod` sz))) (noRealUnary "rotateR") (rot False sz y) (noFloatUnary "rotateR") (noDoubleUnary "rotateR") x + | isBounded x = let sz = ghcBitSize x in liftSym1 (mkSymOp1 (Ror (y `mod` sz))) (noRealUnary "rotateR") (rot False sz y) (noFloatUnary "rotateR") (noDoubleUnary "rotateR") x | True = shiftR x y -- for unbounded integers, rotateR is the same as shiftR in Haskell -- NB. testBit is *not* implementable on non-concrete symbolic words x `testBit` i @@ -835,7 +854,7 @@ setBitTo x i b = ite b (setBit x i) (clearBit x i) sbvShiftLeft :: (SIntegral a, SIntegral b) => SBV a -> SBV b -> SBV a sbvShiftLeft x i | isSigned i = error "sbvShiftLeft: shift amount should be unsigned" - | True = select [x `shiftL` k | k <- [0 .. bitSize x - 1]] z i + | True = select [x `shiftL` k | k <- [0 .. ghcBitSize x - 1]] z i where z = sbvFromInteger (kindOf x) 0 -- | Generalization of 'shiftR', when the shift-amount is symbolic. Since Haskell's @@ -848,7 +867,7 @@ sbvShiftLeft x i sbvShiftRight :: (SIntegral a, SIntegral b) => SBV a -> SBV b -> SBV a sbvShiftRight x i | isSigned i = error "sbvShiftRight: shift amount should be unsigned" - | True = select [x `shiftR` k | k <- [0 .. bitSize x - 1]] z i + | True = select [x `shiftR` k | k <- [0 .. ghcBitSize x - 1]] z i where z = sbvFromInteger (kindOf x) 0 -- | Arithmetic shift-right with a symbolic unsigned shift amount. This is equivalent @@ -870,7 +889,7 @@ sbvSignedShiftArithRight x i sbvRotateLeft :: (SIntegral a, SIntegral b) => SBV a -> SBV b -> SBV a sbvRotateLeft x i | isSigned i = error "sbvRotateLeft: shift amount should be unsigned" - | True = select [x `rotateL` k | k <- [0 .. bitSize x - 1]] z i + | True = select [x `rotateL` k | k <- [0 .. ghcBitSize x - 1]] z i where z = sbvFromInteger (kindOf x) 0 -- | Generalization of 'rotateR', when the shift-amount is symbolic. Since Haskell's @@ -879,7 +898,7 @@ sbvRotateLeft x i sbvRotateRight :: (SIntegral a, SIntegral b) => SBV a -> SBV b -> SBV a sbvRotateRight x i | isSigned i = error "sbvRotateRight: shift amount should be unsigned" - | True = select [x `rotateR` k | k <- [0 .. bitSize x - 1]] z i + | True = select [x `rotateR` k | k <- [0 .. ghcBitSize x - 1]] z i where z = sbvFromInteger (kindOf x) 0 -- | Full adder. Returns the carry-out from the addition. @@ -902,14 +921,14 @@ fullAdder a b fullMultiplier :: SIntegral a => SBV a -> SBV a -> (SBV a, SBV a) fullMultiplier a b | isSigned a = error "fullMultiplier: only works on unsigned numbers" - | True = (go (bitSize a) 0 a, a*b) + | True = (go (ghcBitSize a) 0 a, a*b) where go 0 p _ = p go n p x = let (c, p') = ite (lsb x) (fullAdder p b) (false, p) (o, p'') = shiftIn c p' (_, x') = shiftIn o x in go (n-1) p'' x' shiftIn k v = (lsb v, mask .|. (v `shiftR` 1)) - where mask = ite k (bit (bitSize v - 1)) 0 + where mask = ite k (bit (ghcBitSize v - 1)) 0 -- | Little-endian blasting of a word into its bits. Also see the 'FromBits' class. blastLE :: (Num a, Bits a, SymWord a) => SBV a -> [SBool] @@ -1071,12 +1090,11 @@ instance SDivisible Integer where instance SDivisible CW where sQuotRem a b | CWInteger x <- cwVal a, CWInteger y <- cwVal b - = let (r1, r2) = sQuotRem x y in (a { cwVal = CWInteger r1 }, b { cwVal = CWInteger r2 }) - --BH FIXME: -2^(n-1) `div` (-1) can overflow. + = let (r1, r2) = sQuotRem x y in (normCW a{ cwVal = CWInteger r1 }, normCW b{ cwVal = CWInteger r2 }) sQuotRem a b = error $ "SBV.sQuotRem: impossible, unexpected args received: " ++ show (a, b) sDivMod a b | CWInteger x <- cwVal a, CWInteger y <- cwVal b - = let (r1, r2) = sDivMod x y in (a { cwVal = CWInteger r1 }, b { cwVal = CWInteger r2 }) + = let (r1, r2) = sDivMod x y in (normCW a { cwVal = CWInteger r1 }, normCW b { cwVal = CWInteger r2 }) sDivMod a b = error $ "SBV.sDivMod: impossible, unexpected args received: " ++ show (a, b) instance SDivisible SWord64 where @@ -1177,6 +1195,24 @@ class Mergeable a where -- The idea is that use symbolicMerge if you know the condition is symbolic, -- otherwise use ite, if there's a chance it might be concrete. ite :: SBool -> a -> a -> a + -- | Branch on a condition, much like 'ite'. The exception is that SBV will + -- check to make sure if the test condition is feasible by making an external + -- call to the SMT solver. Note that this can be expensive, thus we shall use + -- a time-out value ('sBranchTimeOut'). There might be zero, one, or two such + -- external calls per 'sBranch' call: + -- + -- - If condition is statically known to be True/False: 0 calls + -- - In this case, we simply constant fold.. + -- + -- - If condition is determined to be unsatisfiable : 1 call + -- - In this case, we know then-branch is infeasible, so just take the else-branch + -- + -- - If condition is determined to be satisfable : 2 calls + -- - In this case, we know then-branch is feasible, but we still have to check if the else-branch is + -- + -- In summary, 'sBranch' calls can be expensive, but they can help with the so-called symbolic-termination + -- problem. See "Data.SBV.Examples.Misc.SBranch" for an example. + sBranch :: SBool -> a -> a -> a -- | Total indexing operation. @select xs default index@ is intuitively -- the same as @xs !! index@, except it evaluates to @default@ if @index@ -- overflows @@ -1185,6 +1221,7 @@ class Mergeable a where ite s a b | Just t <- unliteral s = if t then a else b | True = symbolicMerge s a b + sBranch s = ite (reduceInPathCondition s) -- NB. Earlier implementation of select used the binary-search trick -- on the index to chop down the search space. While that is a good trick -- in general, it doesn't work for SBV since we do not have any notion of @@ -1200,86 +1237,14 @@ class Mergeable a where -- SBV instance SymWord a => Mergeable (SBV a) where - -- the strict match and checking of literal equivalence is essential below, - -- as otherwise we risk hanging onto huge closures and blow stack! This is - -- against the feel that merging shouldn't look at branches if the test - -- expression is constant. However, it's OK to do it this way since we - -- expect "ite" to be used in such cases which already checks for that. That - -- is the use case of the symbolicMerge should be when the test is symbolic. - -- Of course, we do not have a way of enforcing that in the user code, but - -- at least our library code respects that invariant. - symbolicMerge t a@(SBV{}) b@(SBV{}) - | Just av <- unliteral a, Just bv <- unliteral b, rationalSBVCheck a b, av == bv - = a - | True - = SBV k $ Right $ cache c - where k = kindOf a - c st = do swt <- sbvToSW st t - case () of - () | swt == trueSW -> sbvToSW st a -- these two cases should never be needed as we expect symbolicMerge to be - () | swt == falseSW -> sbvToSW st b -- called with symbolic tests, but just in case.. - () -> do {- It is tempting to record the choice of the test expression here as we branch down to the 'then' and 'else' branches. That is, - when we evaluate 'a', we can make use of the fact that the test expression is True, and similarly we can use the fact that it - is False when b is evaluated. In certain cases this can cut down on symbolic simulation significantly, for instance if - repetitive decisions are made in a recursive loop. Unfortunately, the implementation of this idea is quite tricky, due to - our sharing based implementation. As the 'then' branch is evaluated, we will create many expressions that are likely going - to be "reused" when the 'else' branch is executed. But, it would be *dead wrong* to share those values, as they were "cached" - under the incorrect assumptions. To wit, consider the following: - - foo x y = ite (y .== 0) k (k+1) - where k = ite (y .== 0) x (x+1) - - When we reduce the 'then' branch of the first ite, we'd record the assumption that y is 0. But while reducing the 'then' branch, we'd - like to share 'k', which would evaluate (correctly) to 'x' under the given assumption. When we backtrack and evaluate the 'else' - branch of the first ite, we'd see 'k' is needed again, and we'd look it up from our sharing map to find (incorrectly) that its value - is 'x', which was stored there under the assumption that y was 0, which no longer holds. Clearly, this is unsound. - - A sound implementation would have to precisely track which assumptions were active at the time expressions get shared. That is, - in the above example, we should record that the value of 'k' was cached under the assumption that 'y' is 0. While sound, this - approach unfortunately leads to significant loss of valid sharing when the value itself had nothing to do with the assumption itself. - To wit, consider: - - foo x y = ite (y .== 0) k (k+1) - where k = x+5 - - If we tracked the assumptions, we would recompute 'k' twice, since the branch assumptions would differ. Clearly, there is no need to - re-compute 'k' in this case since its value is independent of y. Note that the whole SBV performance story is based on agressive sharing, - and losing that would have other significant ramifications. - - The "proper" solution would be to track, with each shared computation, precisely which assumptions it actually *depends* on, rather - than blindly recording all the assumptions present at that time. SBV's symbolic simulation engine clearly has all the info needed to do this - properly, but the implementation is not straightforward at all. For each subexpression, we would need to chase down its dependencies - transitively, which can require a lot of scanning of the generated program causing major slow-down; thus potentially defeating the - whole purpose of sharing in the first place. - - Design choice: Keep it simple, and simply do not track the assumption at all. This will maximize sharing, at the cost of evaluating - unreachable branches. I think the simplicity is more important at this point than efficiency. - - Also note that the user can avoid most such issues by properly combining if-then-else's with common conditions together. That is, the - first program above should be written like this: - - foo x y = ite (y .== 0) x (x+2) - - In general, the following transformations should be done whenever possible: - - ite e1 (ite e1 e2 e3) e4 --> ite e1 e2 e4 - ite e1 e2 (ite e1 e3 e4) --> ite e1 e2 e4 - - This is in accordance with the general rule-of-thumb stating conditionals should be avoided as much as possible. However, we might prefer - the following: - - ite e1 (f e2 e4) (f e3 e5) --> f (ite e1 e2 e3) (ite e1 e4 e5) - - especially if this expression happens to be inside 'f's body itself (i.e., when f is recursive), since it reduces the number of - recursive calls. Clearly, programming with symbolic simulation in mind is another kind of beast alltogether. - -} - swa <- sbvToSW st a -- evaluate 'then' branch - swb <- sbvToSW st b -- evaluate 'else' branch - case () of -- merge: - () | swa == swb -> return swa - () | swa == trueSW && swb == falseSW -> return swt - () | swa == falseSW && swb == trueSW -> newExpr st k (SBVApp Not [swt]) - () -> newExpr st k (SBVApp Ite [swt, swa, swb]) + -- sBranch is essentially the default method, but we are careful in not forcing the + -- arguments as ite does, since sBranch is expected to be used when one of the + -- branches is likely to be in a branch that's recursively evaluated. + sBranch s a b + | Just t <- unliteral sReduced = if t then a else b + | True = symbolicWordMerge False sReduced a b + where sReduced = reduceInPathCondition s + symbolicMerge = symbolicWordMerge True -- Custom version of select that translates to SMT-Lib tables at the base type of words select xs err ind | SBV _ (Left c) <- ind = case cwVal c of @@ -1299,6 +1264,86 @@ instance SymWord a => Mergeable (SBV a) where let len = length xs newExpr st kElt (SBVApp (LkUp (idx, kInd, kElt, len) swi swe) []) +-- symbolically merge two SBV words, based on the boolean condition given. +-- The first argument controls whether we want to reduce the branches +-- separately first, which avoids hanging onto huge thunks, and is usually +-- the right thing to do for ite. But we precisely do not want to do that +-- in case of sBranch, which is the case when one of the branches (typically +-- the "else" branch is hanging off of a recursive call. +symbolicWordMerge :: SymWord a => Bool -> SBool -> SBV a -> SBV a -> SBV a +symbolicWordMerge force t a b + | force, Just av <- unliteral a, Just bv <- unliteral b, rationalSBVCheck a b, av == bv + = a + | True + = SBV k $ Right $ cache c + where k = kindOf a + c st = do swt <- sbvToSW st t + case () of + () | swt == trueSW -> sbvToSW st a -- these two cases should never be needed as we expect symbolicMerge to be + () | swt == falseSW -> sbvToSW st b -- called with symbolic tests, but just in case.. + () -> do {- It is tempting to record the choice of the test expression here as we branch down to the 'then' and 'else' branches. That is, + when we evaluate 'a', we can make use of the fact that the test expression is True, and similarly we can use the fact that it + is False when b is evaluated. In certain cases this can cut down on symbolic simulation significantly, for instance if + repetitive decisions are made in a recursive loop. Unfortunately, the implementation of this idea is quite tricky, due to + our sharing based implementation. As the 'then' branch is evaluated, we will create many expressions that are likely going + to be "reused" when the 'else' branch is executed. But, it would be *dead wrong* to share those values, as they were "cached" + under the incorrect assumptions. To wit, consider the following: + + foo x y = ite (y .== 0) k (k+1) + where k = ite (y .== 0) x (x+1) + + When we reduce the 'then' branch of the first ite, we'd record the assumption that y is 0. But while reducing the 'then' branch, we'd + like to share 'k', which would evaluate (correctly) to 'x' under the given assumption. When we backtrack and evaluate the 'else' + branch of the first ite, we'd see 'k' is needed again, and we'd look it up from our sharing map to find (incorrectly) that its value + is 'x', which was stored there under the assumption that y was 0, which no longer holds. Clearly, this is unsound. + + A sound implementation would have to precisely track which assumptions were active at the time expressions get shared. That is, + in the above example, we should record that the value of 'k' was cached under the assumption that 'y' is 0. While sound, this + approach unfortunately leads to significant loss of valid sharing when the value itself had nothing to do with the assumption itself. + To wit, consider: + + foo x y = ite (y .== 0) k (k+1) + where k = x+5 + + If we tracked the assumptions, we would recompute 'k' twice, since the branch assumptions would differ. Clearly, there is no need to + re-compute 'k' in this case since its value is independent of y. Note that the whole SBV performance story is based on agressive sharing, + and losing that would have other significant ramifications. + + The "proper" solution would be to track, with each shared computation, precisely which assumptions it actually *depends* on, rather + than blindly recording all the assumptions present at that time. SBV's symbolic simulation engine clearly has all the info needed to do this + properly, but the implementation is not straightforward at all. For each subexpression, we would need to chase down its dependencies + transitively, which can require a lot of scanning of the generated program causing major slow-down; thus potentially defeating the + whole purpose of sharing in the first place. + + Design choice: Keep it simple, and simply do not track the assumption at all. This will maximize sharing, at the cost of evaluating + unreachable branches. I think the simplicity is more important at this point than efficiency. + + Also note that the user can avoid most such issues by properly combining if-then-else's with common conditions together. That is, the + first program above should be written like this: + + foo x y = ite (y .== 0) x (x+2) + + In general, the following transformations should be done whenever possible: + + ite e1 (ite e1 e2 e3) e4 --> ite e1 e2 e4 + ite e1 e2 (ite e1 e3 e4) --> ite e1 e2 e4 + + This is in accordance with the general rule-of-thumb stating conditionals should be avoided as much as possible. However, we might prefer + the following: + + ite e1 (f e2 e4) (f e3 e5) --> f (ite e1 e2 e3) (ite e1 e4 e5) + + especially if this expression happens to be inside 'f's body itself (i.e., when f is recursive), since it reduces the number of + recursive calls. Clearly, programming with symbolic simulation in mind is another kind of beast alltogether. + -} + swa <- sbvToSW (st `extendPathCondition` (&&& t)) a -- evaluate 'then' branch + swb <- sbvToSW (st `extendPathCondition` (&&& bnot t)) b -- evaluate 'else' branch + case () of -- merge: + () | swa == swb -> return swa + () | swa == trueSW && swb == falseSW -> return swt + () | swa == falseSW && swb == trueSW -> newExpr st k (SBVApp Not [swt]) + () -> newExpr st k (SBVApp Ite [swt, swa, swb]) + -- Unit instance Mergeable () where symbolicMerge _ _ _ = () @@ -1400,10 +1445,10 @@ instance (SymWord a, Bounded a) => Bounded (SBV a) where -- SArrays are both "EqSymbolic" and "Mergeable" instance EqSymbolic (SArray a b) where - (SArray _ a) .== (SArray _ b) = SBV (KBounded False 1) $ Right $ cache c + (SArray _ a) .== (SArray _ b) = SBV KBool $ Right $ cache c where c st = do ai <- uncacheAI a st bi <- uncacheAI b st - newExpr st (KBounded False 1) (SBVApp (ArrEq ai bi) []) + newExpr st KBool (SBVApp (ArrEq ai bi) []) instance SymWord b => Mergeable (SArray a b) where symbolicMerge = mergeArrays @@ -1460,12 +1505,6 @@ instance HasKind a => Uninterpreted (SBV a) where | True = do newUninterpreted st nm (SBVType [ka]) (fst `fmap` mbCgData) newExpr st ka $ SBVApp (Uninterpreted nm) [] --- Forcing an argument; this is a necessary evil to make sure all the arguments --- to an uninterpreted function are evaluated before called; the semantics of --- such functions is necessarily strict; deviating from Haskell's -forceArg :: SW -> IO () -forceArg (SW k n) = k `seq` n `seq` return () - -- Functions of one argument instance (SymWord b, HasKind a) => Uninterpreted (SBV b -> SBV a) where sbvUninterpret mbCgData nm = f @@ -1479,7 +1518,7 @@ instance (SymWord b, HasKind a) => Uninterpreted (SBV b -> SBV a) where result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0) | True = do newUninterpreted st nm (SBVType [kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 - mapM_ forceArg [sw0] + mapM_ forceSWArg [sw0] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0] -- Functions of two arguments @@ -1497,7 +1536,7 @@ instance (SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV c -> SBV b -> S | True = do newUninterpreted st nm (SBVType [kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 - mapM_ forceArg [sw0, sw1] + mapM_ forceSWArg [sw0, sw1] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0, sw1] -- Functions of three arguments @@ -1517,7 +1556,7 @@ instance (SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV d -> sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 - mapM_ forceArg [sw0, sw1, sw2] + mapM_ forceSWArg [sw0, sw1, sw2] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0, sw1, sw2] -- Functions of four arguments @@ -1539,7 +1578,7 @@ instance (SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterprete sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 sw3 <- sbvToSW st arg3 - mapM_ forceArg [sw0, sw1, sw2, sw3] + mapM_ forceSWArg [sw0, sw1, sw2, sw3] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0, sw1, sw2, sw3] -- Functions of five arguments @@ -1563,7 +1602,7 @@ instance (SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => U sw2 <- sbvToSW st arg2 sw3 <- sbvToSW st arg3 sw4 <- sbvToSW st arg4 - mapM_ forceArg [sw0, sw1, sw2, sw3, sw4] + mapM_ forceSWArg [sw0, sw1, sw2, sw3, sw4] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0, sw1, sw2, sw3, sw4] -- Functions of six arguments @@ -1589,7 +1628,7 @@ instance (SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasK sw3 <- sbvToSW st arg3 sw4 <- sbvToSW st arg4 sw5 <- sbvToSW st arg5 - mapM_ forceArg [sw0, sw1, sw2, sw3, sw4, sw5] + mapM_ forceSWArg [sw0, sw1, sw2, sw3, sw4, sw5] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0, sw1, sw2, sw3, sw4, sw5] -- Functions of seven arguments @@ -1618,7 +1657,7 @@ instance (SymWord h, SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymW sw4 <- sbvToSW st arg4 sw5 <- sbvToSW st arg5 sw6 <- sbvToSW st arg6 - mapM_ forceArg [sw0, sw1, sw2, sw3, sw4, sw5, sw6] + mapM_ forceSWArg [sw0, sw1, sw2, sw3, sw4, sw5, sw6] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0, sw1, sw2, sw3, sw4, sw5, sw6] -- Uncurried functions of two arguments @@ -1655,7 +1694,39 @@ instance (SymWord h, SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymW sbvUninterpret mbCgData nm = let f = sbvUninterpret (uc7 `fmap` mbCgData) nm in \(arg0, arg1, arg2, arg3, arg4, arg5, arg6) -> f arg0 arg1 arg2 arg3 arg4 arg5 arg6 where uc7 (cs, fn) = (cs, \a b c d e f g -> fn (a, b, c, d, e, f, g)) --- | Adding arbitrary constraints. +-- | Adding arbitrary constraints. When adding constraints, one has to be careful about +-- making sure they are not inconsistent. The function 'isVacuous' can be use for this purpose. +-- Here is an example. Consider the following predicate: +-- +-- >>> let pred = do { x <- forall "x"; constrain $ x .< x; return $ x .>= (5 :: SWord8) } +-- +-- This predicate asserts that all 8-bit values are larger than 5, subject to the constraint that the +-- values considered satisfy @x .< x@, i.e., they are less than themselves. Since there are no values that +-- satisfy this constraint, the proof will pass vacuously: +-- +-- >>> prove pred +-- Q.E.D. +-- +-- We can use 'isVacuous' to make sure to see that the pass was vacuous: +-- +-- >>> isVacuous pred +-- True +-- +-- While the above example is trivial, things can get complicated if there are multiple constraints with +-- non-straightforward relations; so if constraints are used one should make sure to check the predicate +-- is not vacuously true. Here's an example that is not vacuous: +-- +-- >>> let pred' = do { x <- forall "x"; constrain $ x .> 6; return $ x .>= (5 :: SWord8) } +-- +-- This time the proof passes as expected: +-- +-- >>> prove pred' +-- Q.E.D. +-- +-- And the proof is not vacuous: +-- +-- >>> isVacuous pred' +-- False constrain :: SBool -> Symbolic () constrain c = addConstraint Nothing c (bnot c) @@ -1665,6 +1736,25 @@ constrain c = addConstraint Nothing c (bnot c) pConstrain :: Double -> SBool -> Symbolic () pConstrain t c = addConstraint (Just t) c (bnot c) +-- | Boolean symbolic reduction. See if we can reduce a boolean condition to true/false +-- using the path context information, by making external calls to the SMT solvers. Used in the +-- implementation of 'sBranch'. +reduceInPathCondition :: SBool -> SBool +reduceInPathCondition b + | isConcrete b = b -- No reduction is needed, already a concrete value + | True = SBV k $ Right $ cache c + where k = kindOf b + c st = do -- Now that we know our boolean is not obviously true/false. Need to make an external + -- call to the SMT solver to see if we can prove it is necessarily one of those + let pc = getPathCondition st + satTrue <- isSBranchFeasibleInState st "then" (pc &&& b) + if not satTrue + then return falseSW -- condition is not satisfiable; so it must be necessarily False. + else do satFalse <- isSBranchFeasibleInState st "else" (pc &&& bnot b) + if not satFalse -- negation of the condition is not satisfiable; so it must be necessarily True. + then return trueSW + else sbvToSW st b -- condition is not necessarily always True/False. So, keep symbolic. + -- Quickcheck interface on symbolic-booleans.. instance Testable SBool where property (SBV _ (Left b)) = property (cwToBool b) @@ -1703,5 +1793,9 @@ slet x f = SBV k $ Right $ cache r res = f xsbv sbvToSW st res +-- We use 'isVacuous' and 'prove' only for the "test" section in this file, and GHC complains about that. So, this shuts it up. +__unused :: a +__unused = error "__unused" (isVacuous :: SBool -> IO Bool) (prove :: SBool -> IO ThmResult) + {-# ANN module "HLint: ignore Eta reduce" #-} {-# ANN module "HLint: ignore Reduce duplication" #-} diff --git a/sbv/Data/SBV/BitVectors/PrettyNum.hs b/sbv/Data/SBV/BitVectors/PrettyNum.hs index 28e928d4..4f15fe15 100644 --- a/sbv/Data/SBV/BitVectors/PrettyNum.hs +++ b/sbv/Data/SBV/BitVectors/PrettyNum.hs @@ -27,7 +27,6 @@ import Data.Word (Word8, Word16, Word32, Word64) import Numeric (showIntAtBase, showHex, readInt) import Data.SBV.BitVectors.Data -import Data.SBV.BitVectors.Model () -- instances only -- | PrettyNum class captures printing of numbers in hex and binary formats; also supporting negative numbers. -- diff --git a/sbv/Data/SBV/BitVectors/SignCast.hs b/sbv/Data/SBV/BitVectors/SignCast.hs index 8a1135fa..4d34ddb3 100644 --- a/sbv/Data/SBV/BitVectors/SignCast.hs +++ b/sbv/Data/SBV/BitVectors/SignCast.hs @@ -81,6 +81,7 @@ genericSign x | Just c <- unliteral x = literal $ fromIntegral c | True = SBV k (Right (cache y)) where k = case kindOf x of + KBool -> error "Data.SBV.SignCast.genericSign: Called on boolean value" KBounded False n -> KBounded True n KBounded True _ -> error "Data.SBV.SignCast.genericSign: Called on signed value" KUnbounded -> error "Data.SBV.SignCast.genericSign: Called on unbounded value" @@ -97,6 +98,7 @@ genericUnsign x | Just c <- unliteral x = literal $ fromIntegral c | True = SBV k (Right (cache y)) where k = case kindOf x of + KBool -> error "Data.SBV.SignCast.genericUnSign: Called on boolean value" KBounded True n -> KBounded False n KBounded False _ -> error "Data.SBV.SignCast.genericUnSign: Called on unsigned value" KUnbounded -> error "Data.SBV.SignCast.genericUnSign: Called on unbounded value" diff --git a/sbv/Data/SBV/Bridge/Boolector.hs b/sbv/Data/SBV/Bridge/Boolector.hs index 78399605..0254c688 100644 --- a/sbv/Data/SBV/Bridge/Boolector.hs +++ b/sbv/Data/SBV/Bridge/Boolector.hs @@ -14,10 +14,12 @@ -- - "Data.SBV.Bridge.Z3" -- -- - "Data.SBV.Bridge.CVC4" +-- +-- - "Data.SBV.Bridge.MathSAT" --------------------------------------------------------------------------------- module Data.SBV.Bridge.Boolector ( - -- * CVC4 specific interface + -- * Boolector specific interface sbvCurrentSolver -- ** Proving and checking satisfiability , prove, sat, allSat, isVacuous, isTheorem, isSatisfiable diff --git a/sbv/Data/SBV/Bridge/CVC4.hs b/sbv/Data/SBV/Bridge/CVC4.hs index 584e5683..594a61fc 100644 --- a/sbv/Data/SBV/Bridge/CVC4.hs +++ b/sbv/Data/SBV/Bridge/CVC4.hs @@ -14,6 +14,8 @@ -- - "Data.SBV.Bridge.Z3" -- -- - "Data.SBV.Bridge.Boolector" +-- +-- - "Data.SBV.Bridge.MathSAT" --------------------------------------------------------------------------------- module Data.SBV.Bridge.CVC4 ( diff --git a/sbv/Data/SBV/Bridge/MathSAT.hs b/sbv/Data/SBV/Bridge/MathSAT.hs new file mode 100644 index 00000000..51f28561 --- /dev/null +++ b/sbv/Data/SBV/Bridge/MathSAT.hs @@ -0,0 +1,107 @@ +--------------------------------------------------------------------------------- +-- | +-- Module : Data.SBV.Bridge.MathSAT +-- Copyright : (c) Levent Erkok +-- License : BSD3 +-- Maintainer : erkokl@gmail.com +-- Stability : experimental +-- +-- Interface to the MathSAT SMT solver. Import this module if you want to use the +-- MathSAT SMT prover as your backend solver. Also see: +-- +-- - "Data.SBV.Bridge.Yices" +-- +-- - "Data.SBV.Bridge.Z3" +-- +-- - "Data.SBV.Bridge.CVC4" +-- +-- - "Data.SBV.Bridge.Boolector" +--------------------------------------------------------------------------------- + +module Data.SBV.Bridge.MathSAT ( + -- * MathSAT specific interface + sbvCurrentSolver + -- ** Proving and checking satisfiability + , prove, sat, allSat, isVacuous, isTheorem, isSatisfiable + -- ** Optimization routines + , optimize, minimize, maximize + -- * Non-MathSAT specific SBV interface + -- $moduleExportIntro + , module Data.SBV + ) where + +import Data.SBV hiding (prove, sat, allSat, isVacuous, isTheorem, isSatisfiable, optimize, minimize, maximize, sbvCurrentSolver) + +-- | Current solver instance, pointing to cvc4. +sbvCurrentSolver :: SMTConfig +sbvCurrentSolver = mathSAT + +-- | Prove theorems, using the CVC4 SMT solver +prove :: Provable a + => a -- ^ Property to check + -> IO ThmResult -- ^ Response from the SMT solver, containing the counter-example if found +prove = proveWith sbvCurrentSolver + +-- | Find satisfying solutions, using the CVC4 SMT solver +sat :: Provable a + => a -- ^ Property to check + -> IO SatResult -- ^ Response of the SMT Solver, containing the model if found +sat = satWith sbvCurrentSolver + +-- | Find all satisfying solutions, using the CVC4 SMT solver +allSat :: Provable a + => a -- ^ Property to check + -> IO AllSatResult -- ^ List of all satisfying models +allSat = allSatWith sbvCurrentSolver + +-- | Check vacuity of the explicit constraints introduced by calls to the 'constrain' function, using the CVC4 SMT solver +isVacuous :: Provable a + => a -- ^ Property to check + -> IO Bool -- ^ True if the constraints are unsatisifiable +isVacuous = isVacuousWith sbvCurrentSolver + +-- | Check if the statement is a theorem, with an optional time-out in seconds, using the CVC4 SMT solver +isTheorem :: Provable a + => Maybe Int -- ^ Optional time-out, specify in seconds + -> a -- ^ Property to check + -> IO (Maybe Bool) -- ^ Returns Nothing if time-out expires +isTheorem = isTheoremWith sbvCurrentSolver + +-- | Check if the statement is satisfiable, with an optional time-out in seconds, using the CVC4 SMT solver +isSatisfiable :: Provable a + => Maybe Int -- ^ Optional time-out, specify in seconds + -> a -- ^ Property to check + -> IO (Maybe Bool) -- ^ Returns Nothing if time-out expiers +isSatisfiable = isSatisfiableWith sbvCurrentSolver + +-- | Optimize cost functions, using the CVC4 SMT solver +optimize :: (SatModel a, SymWord a, Show a, SymWord c, Show c) + => OptimizeOpts -- ^ Parameters to optimization (Iterative, Quantified, etc.) + -> (SBV c -> SBV c -> SBool) -- ^ Betterness check: This is the comparison predicate for optimization + -> ([SBV a] -> SBV c) -- ^ Cost function + -> Int -- ^ Number of inputs + -> ([SBV a] -> SBool) -- ^ Validity function + -> IO (Maybe [a]) -- ^ Returns Nothing if there is no valid solution, otherwise an optimal solution +optimize = optimizeWith sbvCurrentSolver + +-- | Minimize cost functions, using the CVC4 SMT solver +minimize :: (SatModel a, SymWord a, Show a, SymWord c, Show c) + => OptimizeOpts -- ^ Parameters to optimization (Iterative, Quantified, etc.) + -> ([SBV a] -> SBV c) -- ^ Cost function to minimize + -> Int -- ^ Number of inputs + -> ([SBV a] -> SBool) -- ^ Validity function + -> IO (Maybe [a]) -- ^ Returns Nothing if there is no valid solution, otherwise an optimal solution +minimize = minimizeWith sbvCurrentSolver + +-- | Maximize cost functions, using the CVC4 SMT solver +maximize :: (SatModel a, SymWord a, Show a, SymWord c, Show c) + => OptimizeOpts -- ^ Parameters to optimization (Iterative, Quantified, etc.) + -> ([SBV a] -> SBV c) -- ^ Cost function to maximize + -> Int -- ^ Number of inputs + -> ([SBV a] -> SBool) -- ^ Validity function + -> IO (Maybe [a]) -- ^ Returns Nothing if there is no valid solution, otherwise an optimal solution +maximize = maximizeWith sbvCurrentSolver + +{- $moduleExportIntro +The remainder of the SBV library that is common to all back-end SMT solvers, directly coming from the "Data.SBV" module. +-} diff --git a/sbv/Data/SBV/Bridge/Yices.hs b/sbv/Data/SBV/Bridge/Yices.hs index 2b2e15a0..5f00a2f1 100644 --- a/sbv/Data/SBV/Bridge/Yices.hs +++ b/sbv/Data/SBV/Bridge/Yices.hs @@ -14,6 +14,8 @@ -- - "Data.SBV.Bridge.CVC4" -- -- - "Data.SBV.Bridge.Z3" +-- +-- - "Data.SBV.Bridge.MathSAT" --------------------------------------------------------------------------------- module Data.SBV.Bridge.Yices ( diff --git a/sbv/Data/SBV/Bridge/Z3.hs b/sbv/Data/SBV/Bridge/Z3.hs index c454bc6d..300a0895 100644 --- a/sbv/Data/SBV/Bridge/Z3.hs +++ b/sbv/Data/SBV/Bridge/Z3.hs @@ -14,6 +14,8 @@ -- - "Data.SBV.Bridge.CVC4" -- -- - "Data.SBV.Bridge.Yices" +-- +-- - "Data.SBV.Bridge.MathSAT" --------------------------------------------------------------------------------- module Data.SBV.Bridge.Z3 ( diff --git a/sbv/Data/SBV/Compilers/C.hs b/sbv/Data/SBV/Compilers/C.hs index 12f119ee..4bc893d9 100644 --- a/sbv/Data/SBV/Compilers/C.hs +++ b/sbv/Data/SBV/Compilers/C.hs @@ -24,7 +24,6 @@ import System.Random import Text.PrettyPrint.HughesPJ import Data.SBV.BitVectors.Data -import Data.SBV.BitVectors.AlgReals import Data.SBV.BitVectors.PrettyNum (shex, showCFloat, showCDouble) import Data.SBV.Compilers.CodeGen @@ -162,6 +161,7 @@ showCType = show . kindOf -- | The printf specifier for the type specifier :: CgConfig -> SW -> Doc specifier cfg sw = case kindOf sw of + KBool -> spec (False, 1) KBounded b i -> spec (b, i) KUnbounded -> spec (True, fromJust (cgInteger cfg)) KReal -> specF (fromJust (cgReal cfg)) @@ -443,7 +443,7 @@ genCProg cfg fn proto (Result kindInfo _tvals cgs ins preConsts tbls arrs _ _ (S len (KFloat{}) = 6 -- SFloat len (KDouble{}) = 7 -- SDouble len (KUnbounded{}) = 8 - len (KBounded False 1) = 5 -- SBool + len KBool = 5 -- SBool len (KBounded False n) = 5 + length (show n) -- SWordN len (KBounded True n) = 4 + length (show n) -- SIntN len (KUninterpreted s) = die $ "Uninterpreted sort: " ++ s @@ -499,8 +499,8 @@ ppExpr cfg consts (SBVApp op opArgs) = p op (map (showSW cfg consts) opArgs) p (Shr i) [a] = shift False i a (head opArgs) p Not [a] = case kindOf (head opArgs) of -- be careful about booleans, bitwise complement is not correct for them! - KBounded False 1 -> text "!" <> a - _ -> text "~" <> a + KBool -> text "!" <> a + _ -> text "~" <> a p Ite [a, b, c] = a <+> text "?" <+> b <+> text ":" <+> c p (LkUp (t, k, _, len) ind def) [] | not rtc = lkUp -- ignore run-time-checks per user request @@ -517,6 +517,7 @@ ppExpr cfg consts (SBVApp op opArgs) = p op (map (showSW cfg consts) opArgs) canOverflow True sz = (2::Integer)^(sz-1)-1 >= fromIntegral len canOverflow False sz = (2::Integer)^sz -1 >= fromIntegral len (needsCheckL, needsCheckR) = case k of + KBool -> (False, canOverflow False (1::Int)) KBounded sg sz -> (sg, canOverflow sg sz) KReal -> die "array index with real value" KFloat -> die "array index with float value" diff --git a/sbv/Data/SBV/Compilers/CodeGen.hs b/sbv/Data/SBV/Compilers/CodeGen.hs index 04744acf..5a7229a0 100644 --- a/sbv/Data/SBV/Compilers/CodeGen.hs +++ b/sbv/Data/SBV/Compilers/CodeGen.hs @@ -16,6 +16,7 @@ module Data.SBV.Compilers.CodeGen where import Control.Monad.Trans import Control.Monad.State.Lazy +import Control.Applicative (Applicative) import Data.Char (toLower, isSpace) import Data.List (nub, isPrefixOf, intercalate, (\\)) import System.Directory (createDirectory, doesDirectoryExist, doesFileExist) @@ -75,7 +76,7 @@ initCgState = CgState { -- reference parameters (for returning composite values in languages such as C), -- and return values. newtype SBVCodeGen a = SBVCodeGen (StateT CgState Symbolic a) - deriving (Monad, MonadIO, MonadState CgState) + deriving (Applicative, Functor, Monad, MonadIO, MonadState CgState) -- | Reach into symbolic monad from code-generation liftSymbolic :: Symbolic a -> SBVCodeGen a @@ -109,7 +110,7 @@ data CgSRealType = CgFloat -- ^ @float@ | CgLongDouble -- ^ @long double@ deriving Eq --- As they would be used in a C program +-- | 'Show' instance for 'cgSRealType' displays values as they would be used in a C program instance Show CgSRealType where show CgFloat = "float" show CgDouble = "double" @@ -217,6 +218,7 @@ isCgMakefile :: CgPgmKind -> Bool isCgMakefile CgMakefile{} = True isCgMakefile _ = False +-- | A simple way to print bundles, mostly for debugging purposes. instance Show CgPgmBundle where show (CgPgmBundle _ fs) = intercalate "\n" $ map showFile fs where showFile :: (FilePath, (CgPgmKind, [Doc])) -> String diff --git a/sbv/Data/SBV/Internals.hs b/sbv/Data/SBV/Internals.hs index bb6a27ea..7c9bd8f6 100644 --- a/sbv/Data/SBV/Internals.hs +++ b/sbv/Data/SBV/Internals.hs @@ -15,16 +15,13 @@ module Data.SBV.Internals ( -- * Running symbolic programs /manually/ Result, SBVRunMode(..), runSymbolic, runSymbolic' -- * Other internal structures useful for low-level programming - , SBV(..), slet, CW, mkConstCW, genVar, genVar_ - , mkSymSBVWithRandom - , Quantifier(..) + , SBV(..), slet, CW(..), Kind(..), CWVal(..), AlgReal(..), mkConstCW, genVar, genVar_ , liftQRem, liftDMod -- * Compilation to C , compileToC', compileToCLib', CgPgmBundle(..), CgPgmKind(..) ) where -import Data.SBV.BitVectors.Data (Result, SBVRunMode(..), runSymbolic, runSymbolic' - , SBV(..), CW, mkConstCW, Quantifier(..), mkSymSBVWithRandom) +import Data.SBV.BitVectors.Data (Result, SBVRunMode(..), runSymbolic, runSymbolic', SBV(..), CW(..), Kind(..), CWVal(..), AlgReal(..), mkConstCW) import Data.SBV.BitVectors.Model (genVar, genVar_, slet, liftQRem, liftDMod) import Data.SBV.Compilers.C (compileToC', compileToCLib') import Data.SBV.Compilers.CodeGen (CgPgmBundle(..), CgPgmKind(..)) diff --git a/sbv/Data/SBV/Provers/Boolector.hs b/sbv/Data/SBV/Provers/Boolector.hs index b89c8d75..153eaf38 100644 --- a/sbv/Data/SBV/Provers/Boolector.hs +++ b/sbv/Data/SBV/Provers/Boolector.hs @@ -26,10 +26,10 @@ import Data.SBV.SMT.SMTLib -- | The description of the Boolector SMT solver -- The default executable is @\"boolector\"@, which must be in your path. You can use the @SBV_BOOLECTOR@ environment variable to point to the executable on your system. --- The default options are @\"--lang smt\"@. You can use the @SBV_BOOLECTOR_OPTIONS@ environment variable to override the options. +-- The default options are @\"-m --smt2\"@. You can use the @SBV_BOOLECTOR_OPTIONS@ environment variable to override the options. boolector :: SMTSolver boolector = SMTSolver { - name = "boolector" + name = Boolector , executable = "boolector" , options = ["-m", "--smt2"] , engine = \cfg _isSat qinps modelMap _skolemMap pgm -> do diff --git a/sbv/Data/SBV/Provers/CVC4.hs b/sbv/Data/SBV/Provers/CVC4.hs index c875aa14..2e930945 100644 --- a/sbv/Data/SBV/Provers/CVC4.hs +++ b/sbv/Data/SBV/Provers/CVC4.hs @@ -30,7 +30,7 @@ import Data.SBV.SMT.SMTLib -- The default options are @\"--lang smt\"@. You can use the @SBV_CVC4_OPTIONS@ environment variable to override the options. cvc4 :: SMTSolver cvc4 = SMTSolver { - name = "cvc4" + name = CVC4 , executable = "cvc4" , options = ["--lang", "smt"] , engine = \cfg isSat qinps modelMap skolemMap pgm -> do @@ -57,7 +57,7 @@ cvc4 = SMTSolver { } } where zero :: Kind -> String - zero (KBounded False 1) = "#b0" + zero KBool = "false" zero (KBounded _ sz) = "#x" ++ replicate (sz `div` 4) '0' zero KUnbounded = "0" zero KReal = "0.0" diff --git a/sbv/Data/SBV/Provers/MathSAT.hs b/sbv/Data/SBV/Provers/MathSAT.hs new file mode 100644 index 00000000..5fc27735 --- /dev/null +++ b/sbv/Data/SBV/Provers/MathSAT.hs @@ -0,0 +1,92 @@ +----------------------------------------------------------------------------- +-- | +-- Module : Data.SBV.Provers.MathSAT +-- Copyright : (c) Levent Erkok +-- License : BSD3 +-- Maintainer : erkokl@gmail.com +-- Stability : experimental +-- +-- The connection to the MathSAT SMT solver +----------------------------------------------------------------------------- + +{-# LANGUAGE ScopedTypeVariables #-} + +module Data.SBV.Provers.MathSAT(mathSAT) where + +import qualified Control.Exception as C + +import Data.Function (on) +import Data.List (sortBy, intercalate) +import System.Environment (getEnv) + +import Data.SBV.BitVectors.Data +import Data.SBV.SMT.SMT +import Data.SBV.SMT.SMTLib + +-- | The description of the MathSAT SMT solver +-- The default executable is @\"mathsat\"@, which must be in your path. You can use the @SBV_MATHSAT@ environment variable to point to the executable on your system. +-- The default options are @\"-input=smt2\"@. You can use the @SBV_MATHSAT_OPTIONS@ environment variable to override the options. +mathSAT :: SMTSolver +mathSAT = SMTSolver { + name = MathSAT + , executable = "mathsat" + , options = ["-input=smt2"] + , engine = \cfg _isSat qinps modelMap skolemMap pgm -> do + execName <- getEnv "SBV_MATHSAT" `C.catch` (\(_ :: C.SomeException) -> return (executable (solver cfg))) + execOpts <- (words `fmap` getEnv "SBV_MATHSAT_OPTIONS") `C.catch` (\(_ :: C.SomeException) -> return (options (solver cfg))) + let cfg' = cfg { solver = (solver cfg) {executable = execName, options = addTimeOut (timeOut cfg) execOpts} + } + tweaks = case solverTweaks cfg' of + [] -> "" + ts -> unlines $ "; --- user given solver tweaks ---" : ts ++ ["; --- end of user given tweaks ---"] + script = SMTScript {scriptBody = tweaks ++ pgm, scriptModel = Just (cont skolemMap)} + standardSolver cfg' script id (ProofError cfg') (interpretSolverOutput cfg' (extractMap (map snd qinps) modelMap)) + , xformExitCode = id + , capabilities = SolverCapabilities { + capSolverName = "MathSAT" + , mbDefaultLogic = Nothing + , supportsMacros = False + , supportsProduceModels = True + , supportsQuantifiers = True + , supportsUninterpretedSorts = True + , supportsUnboundedInts = True + , supportsReals = True + , supportsFloats = False + , supportsDoubles = False + } + } + where zero :: Kind -> String + zero KBool = "false" + zero (KBounded _ sz) = "#x" ++ replicate (sz `div` 4) '0' + zero KUnbounded = "0" + zero KReal = "0.0" + zero KFloat = error "SBV.MathSAT.zero: Unexpected sort SFloat" + zero KDouble = error "SBV.MathSAT.zero: Unexpected sort SDouble" + zero (KUninterpreted s) = error $ "SBV.MathSAT.zero: Unexpected uninterpreted sort: " ++ s + cont skolemMap = intercalate "\n" $ concatMap extract skolemMap + where -- In the skolemMap: + -- * Left's are universals: i.e., the model should be true for + -- any of these. So, we simply "echo 0" for these values. + -- * Right's are existentials. If there are no dependencies (empty list), then we can + -- simply use get-value to extract it's value. Otherwise, we have to apply it to + -- an appropriate number of 0's to get the final value. + extract (Left s) = ["(echo \"((" ++ show s ++ " " ++ zero (kindOf s) ++ "))\")"] + extract (Right (s, [])) = ["(get-value (" ++ show s ++ "))"] + extract (Right (s, ss)) = let g = "(get-value ((" ++ show s ++ concat [' ' : zero (kindOf a) | a <- ss] ++ ")))" in [g] + addTimeOut Nothing o = o + addTimeOut (Just _) _ = error "MathSAT: Timeout values are not supported by MathSat" + +extractMap :: [NamedSymVar] -> [(String, UnintKind)] -> [String] -> SMTModel +extractMap inps _modelMap solverLines = + SMTModel { modelAssocs = map snd $ sortByNodeId $ concatMap (interpretSolverModelLine inps . cvt) solverLines + , modelUninterps = [] + , modelArrays = [] + } + where sortByNodeId :: [(Int, a)] -> [(Int, a)] + sortByNodeId = sortBy (compare `on` fst) + cvt :: String -> String + cvt s = case words s of + [var, val] -> "((" ++ var ++ " #b" ++ map tr val ++ "))" + _ -> s -- good-luck.. + where tr 'x' = '0' + tr x = x diff --git a/sbv/Data/SBV/Provers/Prover.hs b/sbv/Data/SBV/Provers/Prover.hs index 3135ac1a..8df9ab31 100644 --- a/sbv/Data/SBV/Provers/Prover.hs +++ b/sbv/Data/SBV/Provers/Prover.hs @@ -18,55 +18,53 @@ module Data.SBV.Provers.Prover ( SMTSolver(..), SMTConfig(..), Predicate, Provable(..) , ThmResult(..), SatResult(..), AllSatResult(..), SMTResult(..) , isSatisfiable, isSatisfiableWith, isTheorem, isTheoremWith - , Equality(..) , prove, proveWith , sat, satWith , allSat, allSatWith , isVacuous, isVacuousWith - , solve , SatModel(..), Modelable(..), displayModels, extractModels - , boolector, cvc4, yices, z3, defaultSMTCfg + , getModelDictionaries, getModelValues, getModelUninterpretedValues + , boolector, cvc4, yices, z3, mathSAT, defaultSMTCfg , compileToSMTLib, generateSMTBenchmarks - , sbvCheckSolverInstallation + , isSBranchFeasibleInState , internalSatWith, internalIsSatisfiableWith, internalIsSatisfiable , internalProveWith, internalIsTheoremWith, internalIsTheorem ) where -import qualified Control.Exception as E - -import Control.Concurrent (forkIO, newChan, writeChan, getChanContents) -import Control.Monad (when, unless, void) -import Control.Monad.Trans(liftIO) -import Data.List (intercalate) -import Data.Maybe (fromJust, isJust, mapMaybe) -import System.FilePath (addExtension) -import System.Time (getClockTime) +import Control.Monad (when, unless) +import Control.Monad.Trans (liftIO) +import Data.List (intercalate) +import Data.Maybe (mapMaybe, fromMaybe) +import System.FilePath (addExtension, splitExtension) +import System.Time (getClockTime) +import System.IO.Unsafe (unsafeInterleaveIO) import qualified Data.Set as Set (Set, toList) import Data.SBV.BitVectors.Data -import Data.SBV.BitVectors.Model import Data.SBV.SMT.SMT import Data.SBV.SMT.SMTLib import qualified Data.SBV.Provers.Boolector as Boolector import qualified Data.SBV.Provers.CVC4 as CVC4 import qualified Data.SBV.Provers.Yices as Yices import qualified Data.SBV.Provers.Z3 as Z3 +import qualified Data.SBV.Provers.MathSAT as MathSAT import Data.SBV.Utils.TDiff -import Data.SBV.Utils.Boolean mkConfig :: SMTSolver -> Bool -> [String] -> SMTConfig -mkConfig s isSMTLib2 tweaks = SMTConfig { verbose = False - , timing = False - , timeOut = Nothing - , printBase = 10 - , printRealPrec = 16 - , smtFile = Nothing - , solver = s - , solverTweaks = tweaks - , useSMTLib2 = isSMTLib2 - , satCmd = "(check-sat)" - , roundingMode = RoundNearestTiesToEven +mkConfig s isSMTLib2 tweaks = SMTConfig { verbose = False + , timing = False + , sBranchTimeOut = Nothing + , timeOut = Nothing + , printBase = 10 + , printRealPrec = 16 + , smtFile = Nothing + , solver = s + , solverTweaks = tweaks + , useSMTLib2 = isSMTLib2 + , satCmd = "(check-sat)" + , roundingMode = RoundNearestTiesToEven + , useLogic = Nothing } -- | Default configuration for the Boolector SMT solver @@ -83,8 +81,11 @@ yices = mkConfig Yices.yices False [] -- | Default configuration for the Z3 SMT solver z3 :: SMTConfig ---z3 = mkConfig Z3.z3 True ["(set-option :smt.mbqi true) ; use model based quantifier instantiation"] -z3 = mkConfig Z3.z3 True [] +z3 = mkConfig Z3.z3 True ["(set-option :smt.mbqi true) ; use model based quantifier instantiation"] + +-- | Default configuration for the MathSAT SMT solver +mathSAT :: SMTConfig +mathSAT = mkConfig MathSAT.mathSAT True [] -- | The default solver used by SBV. This is currently set to z3. defaultSMTCfg :: SMTConfig @@ -233,16 +234,6 @@ prove = proveWith defaultSMTCfg sat :: Provable a => a -> IO SatResult sat = satWith defaultSMTCfg --- | Form the symbolic conjunction of a given list of boolean conditions. Useful in expressing --- problems with constraints, like the following: --- --- @ --- do [x, y, z] <- sIntegers [\"x\", \"y\", \"z\"] --- solve [x .> 5, y + z .< x] --- @ -solve :: [SBool] -> Symbolic SBool -solve = return . bAnd - -- | Return all satisfying assignments for a predicate, equivalent to @'allSatWith' 'defaultSMTCfg'@. -- Satisfying assignments are constructed lazily, so they will be available as returned by the solver -- and on demand. @@ -254,40 +245,8 @@ solve = return . bAnd allSat :: Provable a => a -> IO AllSatResult allSat = allSatWith defaultSMTCfg --- | Check if the given constraints are satisfiable, equivalent to @'isVacuousWith' 'defaultSMTCfg'@. This --- call can be used to ensure that the specified constraints (via 'constrain') are satisfiable, i.e., that --- the proof involving these constraints is not passing vacuously. Here is an example. Consider the following --- predicate: --- --- >>> let pred = do { x <- forall "x"; constrain $ x .< x; return $ x .>= (5 :: SWord8) } --- --- This predicate asserts that all 8-bit values are larger than 5, subject to the constraint that the --- values considered satisfy @x .< x@, i.e., they are less than themselves. Since there are no values that --- satisfy this constraint, the proof will pass vacuously: --- --- >>> prove pred --- Q.E.D. --- --- We can use 'isVacuous' to make sure to see that the pass was vacuous: --- --- >>> isVacuous pred --- True --- --- While the above example is trivial, things can get complicated if there are multiple constraints with --- non-straightforward relations; so if constraints are used one should make sure to check the predicate --- is not vacuously true. Here's an example that is not vacuous: --- --- >>> let pred' = do { x <- forall "x"; constrain $ x .> 6; return $ x .>= (5 :: SWord8) } --- --- This time the proof passes as expected: --- --- >>> prove pred' --- Q.E.D. --- --- And the proof is not vacuous: --- --- >>> isVacuous pred' --- False +-- | Check if the given constraints are satisfiable, equivalent to @'isVacuousWith' 'defaultSMTCfg'@. +-- See the function 'constrain' for an example use of 'isVacuous'. isVacuous :: Provable a => a -> IO Bool isVacuous = isVacuousWith defaultSMTCfg @@ -414,7 +373,7 @@ internalSatWith config b = do -- | Determine if the constraints are vacuous using the given SMT-solver isVacuousWith :: Provable a => SMTConfig -> a -> IO Bool isVacuousWith config a = do - Result ki tr uic is cs ts as uis ax asgn cstr _ <- runSymbolic True $ forAll_ a >>= output + Result ki tr uic is cs ts as uis ax asgn cstr _ <- runSymbolic (True, Just config) $ forAll_ a >>= output case cstr of [] -> return False -- no constraints, no need to check _ -> do let is' = [(EX, i) | (_, i) <- is] -- map all quantifiers to "exists" for the constraint check @@ -435,45 +394,40 @@ allSatWith config p = do msg "Checking Satisfiability, all solutions.." sbvPgm@(qinps, _, _, ki, _) <- simulate converter config True [] p let usorts = [s | KUninterpreted s <- Set.toList ki] - unless (null usorts) $ error $ "SBV.allSat: All-sat calls are not supported in the presence of uninterpreted sorts: " ++ unwords usorts - ++ "\n Only 'sat' and 'prove' calls are available when uninterpreted sorts are used." - resChan <- newChan - let add = writeChan resChan . Just - stop = writeChan resChan Nothing - final r = add r >> stop - die m = final (ProofError config [m]) - -- only fork if non-verbose.. otherwise stdout gets garbled - fork io = if verbose config then io else void (forkIO io) - fork $ E.catch (go sbvPgm add stop final (1::Int) []) - (\e -> die (show (e::E.SomeException))) - results <- getChanContents resChan + unless (null usorts) $ msg $ "SBV.allSat: Uninterpreted sorts present: " ++ unwords usorts + ++ "\n SBV will use equivalence classes to generate all-satisfying instances." + results <- unsafeInterleaveIO $ go sbvPgm (1::Int) [] -- See if there are any existentials below any universals -- If such is the case, then the solutions are unique upto prefix existentials let w = ALL `elem` map fst qinps - return $ AllSatResult (w, map fromJust (takeWhile isJust results)) + return $ AllSatResult (w, results) where msg = when (verbose config) . putStrLn . ("** " ++) - go sbvPgm add stop final = loop + go sbvPgm = loop where loop !n nonEqConsts = do curResult <- invoke nonEqConsts n sbvPgm case curResult of - Nothing -> stop - Just (SatResult r) -> case r of - Satisfiable _ (SMTModel [] _ _) -> final r - Unknown _ (SMTModel [] _ _) -> final r - ProofError _ _ -> final r - TimeOut _ -> stop - Unsatisfiable _ -> stop - Satisfiable _ model -> add r >> loop (n+1) (modelAssocs model : nonEqConsts) - Unknown _ model -> add r >> loop (n+1) (modelAssocs model : nonEqConsts) + Nothing -> return [] + Just (SatResult r) -> let cont model = do rest <- unsafeInterleaveIO $ loop (n+1) (modelAssocs model : nonEqConsts) + return (r : rest) + in case r of + Satisfiable _ (SMTModel [] _ _) -> return [r] + Unknown _ (SMTModel [] _ _) -> return [r] + ProofError _ _ -> return [r] + TimeOut _ -> return [] + Unsatisfiable _ -> return [] + Satisfiable _ model -> cont model + Unknown _ model -> cont model invoke nonEqConsts n (qinps, modelMap, skolemMap, _, smtLibPgm) = do msg $ "Looking for solution " ++ show n case addNonEqConstraints (roundingMode config) qinps nonEqConsts smtLibPgm of Nothing -> -- no new constraints added, stop return Nothing Just finalPgm -> do msg $ "Generated SMTLib program:\n" ++ finalPgm - smtAnswer <- engine (solver config) config True qinps modelMap skolemMap finalPgm + smtAnswer <- engine (solver config) (updateName (n-1) config) True qinps modelMap skolemMap finalPgm msg "Done.." return $ Just $ SatResult smtAnswer + updateName i cfg = cfg{smtFile = upd `fmap` smtFile cfg} + where upd nm = let (b, e) = splitExtension nm in b ++ "_allSat_" ++ show i ++ e type SMTProblem = ( [(Quantifier, NamedSymVar)] -- inputs , [(String, UnintKind)] -- model-map @@ -497,7 +451,7 @@ simulate converter config isSat comments predicate = do let msg = when (verbose config) . putStrLn . ("** " ++) isTiming = timing config msg "Starting symbolic simulation.." - res <- timeIf isTiming "problem construction" $ runSymbolic isSat $ (if isSat then forSome_ else forAll_) predicate >>= output + res <- timeIf isTiming "problem construction" $ runSymbolic (isSat, Just config) $ (if isSat then forSome_ else forAll_) predicate >>= output msg $ "Generated symbolic trace:\n" ++ show res msg "Translating to SMT-Lib.." runProofOn converter config isSat comments res @@ -507,7 +461,7 @@ runProofOn converter config isSat comments res = let isTiming = timing config solverCaps = capabilities (solver config) in case res of - Result ki _qcInfo _codeSegs is consts tbls arrs uis axs pgm cstrs [o@(SW (KBounded False 1) _)] -> + Result ki _qcInfo _codeSegs is consts tbls arrs uis axs pgm cstrs [o@(SW KBool _)] -> timeIf isTiming "translation" $ let uiMap = mapMaybe arrayUIKind arrs ++ map unintFnUIKind uis skolemMap = skolemize (if isSat then is else map flipQ is) @@ -518,7 +472,7 @@ runProofOn converter config isSat comments res = where go [] (_, sofar) = reverse sofar go ((ALL, (v, _)):rest) (us, sofar) = go rest (v:us, Left v : sofar) go ((EX, (v, _)):rest) (us, sofar) = go rest (us, Right (v, reverse us) : sofar) - in return (is, uiMap, skolemMap, ki, converter (roundingMode config) solverCaps ki isSat comments is skolemMap consts tbls arrs uis axs pgm cstrs o) + in return (is, uiMap, skolemMap, ki, converter (roundingMode config) (useLogic config) solverCaps ki isSat comments is skolemMap consts tbls arrs uis axs pgm cstrs o) Result _kindInfo _qcInfo _codeSegs _is _consts _tbls _arrs _uis _axs _pgm _cstrs os -> case length os of 0 -> error $ "Impossible happened, unexpected non-outputting result\n" ++ show res 1 -> error $ "Impossible happened, non-boolean output in " ++ show os @@ -527,56 +481,23 @@ runProofOn converter config isSat comments res = ++ "\nDetected while generating the trace:\n" ++ show res ++ "\n*** Check calls to \"output\", they are typically not needed!" --- | Check whether the given solver is installed and is ready to go. This call does a --- simple call to the solver to ensure all is well. -sbvCheckSolverInstallation :: SMTConfig -> IO Bool -sbvCheckSolverInstallation cfg = do ThmResult r <- proveWith cfg $ \x -> (x+x) .== ((x*2) :: SWord8) - case r of - Unsatisfiable _ -> return True - _ -> return False - --- | Equality as a proof method. Allows for --- very concise construction of equivalence proofs, which is very typical in --- bit-precise proofs. -infix 4 === -class Equality a where - (===) :: a -> a -> IO ThmResult - -instance (SymWord a, EqSymbolic z) => Equality (SBV a -> z) where - k === l = prove $ \a -> k a .== l a - -instance (SymWord a, SymWord b, EqSymbolic z) => Equality (SBV a -> SBV b -> z) where - k === l = prove $ \a b -> k a b .== l a b - -instance (SymWord a, SymWord b, EqSymbolic z) => Equality ((SBV a, SBV b) -> z) where - k === l = prove $ \a b -> k (a, b) .== l (a, b) - -instance (SymWord a, SymWord b, SymWord c, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> z) where - k === l = prove $ \a b c -> k a b c .== l a b c - -instance (SymWord a, SymWord b, SymWord c, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c) -> z) where - k === l = prove $ \a b c -> k (a, b, c) .== l (a, b, c) - -instance (SymWord a, SymWord b, SymWord c, SymWord d, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> z) where - k === l = prove $ \a b c d -> k a b c d .== l a b c d - -instance (SymWord a, SymWord b, SymWord c, SymWord d, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d) -> z) where - k === l = prove $ \a b c d -> k (a, b, c, d) .== l (a, b, c, d) - -instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> z) where - k === l = prove $ \a b c d e -> k a b c d e .== l a b c d e - -instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d, SBV e) -> z) where - k === l = prove $ \a b c d e -> k (a, b, c, d, e) .== l (a, b, c, d, e) - -instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> SBV f -> z) where - k === l = prove $ \a b c d e f -> k a b c d e f .== l a b c d e f - -instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d, SBV e, SBV f) -> z) where - k === l = prove $ \a b c d e f -> k (a, b, c, d, e, f) .== l (a, b, c, d, e, f) - -instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, SymWord g, EqSymbolic z) => Equality (SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> SBV f -> SBV g -> z) where - k === l = prove $ \a b c d e f g -> k a b c d e f g .== l a b c d e f g - -instance (SymWord a, SymWord b, SymWord c, SymWord d, SymWord e, SymWord f, SymWord g, EqSymbolic z) => Equality ((SBV a, SBV b, SBV c, SBV d, SBV e, SBV f, SBV g) -> z) where - k === l = prove $ \a b c d e f g -> k (a, b, c, d, e, f, g) .== l (a, b, c, d, e, f, g) +-- | Check if a branch condition is feasible in the current state +isSBranchFeasibleInState :: State -> String -> SBool -> IO Bool +isSBranchFeasibleInState st branch cond = do + let cfg = let pickedConfig = fromMaybe defaultSMTCfg (getSBranchRunConfig st) + in pickedConfig { timeOut = sBranchTimeOut pickedConfig } + msg = when (verbose cfg) . putStrLn . ("** " ++) + sw <- sbvToSW st cond + () <- forceSWArg sw + Result ki tr uic is cs ts as uis ax asgn cstr _ <- liftIO $ extractSymbolicSimulationState st + let -- Construct the corresponding sat-checker for the branch. Note that we need to + -- forget about the quantifiers and just use an "exist", as we're looking for a + -- point-satisfiability check here; whatever the original program was. + pgm = Result ki tr uic [(EX, n) | (_, n) <- is] cs ts as uis ax asgn cstr [sw] + cvt = if useSMTLib2 cfg then toSMTLib2 else toSMTLib1 + check <- runProofOn cvt cfg True [] pgm >>= callSolver True ("sBranch: Checking " ++ show branch ++ " feasibility") SatResult cfg + res <- case check of + SatResult (Unsatisfiable _) -> return False + _ -> return True -- No risks, even if it timed-our or anything else, we say it's feasible + msg $ "sBranch: Conclusion: " ++ if res then "Feasible" else "Unfeasible" + return res diff --git a/sbv/Data/SBV/Provers/SExpr.hs b/sbv/Data/SBV/Provers/SExpr.hs index 696d7788..bfdd6bfa 100644 --- a/sbv/Data/SBV/Provers/SExpr.hs +++ b/sbv/Data/SBV/Provers/SExpr.hs @@ -11,10 +11,9 @@ module Data.SBV.Provers.SExpr where -import Control.Monad.Error () -- for Monad (Either String) instance -import Data.Char (isDigit, ord) -import Data.List (isPrefixOf) -import Numeric (readInt, readDec, readHex, fromRat) +import Data.Char (isDigit, ord) +import Data.List (isPrefixOf) +import Numeric (readInt, readDec, readHex, fromRat) import Data.SBV.BitVectors.AlgReals import Data.SBV.BitVectors.Data (nan, infinity) diff --git a/sbv/Data/SBV/Provers/Yices.hs b/sbv/Data/SBV/Provers/Yices.hs index 6771f31c..886f1539 100644 --- a/sbv/Data/SBV/Provers/Yices.hs +++ b/sbv/Data/SBV/Provers/Yices.hs @@ -31,7 +31,7 @@ import Data.SBV.SMT.SMTLib -- The default options are @\"-m -f\"@, which is valid for Yices 2.1 series. You can use the @SBV_YICES_OPTIONS@ environment variable to override the options. yices :: SMTSolver yices = SMTSolver { - name = "Yices" + name = Yices , executable = "yices-smt" -- , options = ["-tc", "-smt", "-e"] -- For Yices1 , options = ["-m", "-f"] -- For Yices2 diff --git a/sbv/Data/SBV/Provers/Z3.hs b/sbv/Data/SBV/Provers/Z3.hs index 7b3bcf02..23a50fa3 100644 --- a/sbv/Data/SBV/Provers/Z3.hs +++ b/sbv/Data/SBV/Provers/Z3.hs @@ -23,6 +23,7 @@ import qualified System.Info as S(os) import Data.SBV.BitVectors.AlgReals import Data.SBV.BitVectors.Data +import Data.SBV.BitVectors.PrettyNum import Data.SBV.SMT.SMT import Data.SBV.SMT.SMTLib @@ -38,7 +39,7 @@ optionPrefix -- The default options are @\"-in -smt2\"@, which is valid for Z3 4.1. You can use the @SBV_Z3_OPTIONS@ environment variable to override the options. z3 :: SMTSolver z3 = SMTSolver { - name = "z3" + name = Z3 , executable = "z3" , options = map (optionPrefix:) ["in", "smt2"] , engine = \cfg isSat qinps modelMap skolemMap pgm -> do @@ -49,8 +50,8 @@ z3 = SMTSolver { [] -> "" ts -> unlines $ "; --- user given solver tweaks ---" : ts ++ ["; --- end of user given tweaks ---"] dlim = printRealPrec cfg' - --ppDecLim = "(set-option :pp.decimal_precision " ++ show dlim ++ ")\n" - script = SMTScript {scriptBody = tweaks ++ {- ppDecLim ++ -} pgm, scriptModel = Just (cont skolemMap)} + ppDecLim = "(set-option :pp.decimal_precision " ++ show dlim ++ ")\n" + script = SMTScript {scriptBody = tweaks ++ ppDecLim ++ pgm, scriptModel = Just (cont (roundingMode cfg) skolemMap)} if dlim < 1 then error $ "SBV.Z3: printRealPrec value should be at least 1, invalid value received: " ++ show dlim else standardSolver cfg' script cleanErrs (ProofError cfg') (interpretSolverOutput cfg' (extractMap isSat qinps modelMap)) @@ -68,28 +69,27 @@ z3 = SMTSolver { , supportsDoubles = True } } - where -- Get rid of the following when z3_4.0 is out - cleanErrs = intercalate "\n" . filter (not . junk) . lines + where cleanErrs = intercalate "\n" . filter (not . junk) . lines junk = ("WARNING:" `isPrefixOf`) - zero :: Kind -> String - zero (KBounded False 1) = "#b0" - zero (KBounded _ sz) = "#x" ++ replicate (sz `div` 4) '0' - zero KUnbounded = "0" - zero KReal = "0.0" - zero KFloat = error "Z3:TBD: Figure out how to write float constants!" - zero KDouble = error "Z3:TBD: Figure out how to write double constants!" - zero (KUninterpreted s) = error $ "SBV.Z3.zero: Unexpected uninterpreted sort: " ++ s - cont skolemMap = intercalate "\n" $ concatMap extract skolemMap + zero :: RoundingMode -> Kind -> String + zero _ KBool = "false" + zero _ (KBounded _ sz) = "#x" ++ replicate (sz `div` 4) '0' + zero _ KUnbounded = "0" + zero _ KReal = "0.0" + zero rm KFloat = showSMTFloat rm 0 + zero rm KDouble = showSMTDouble rm 0 + zero _ (KUninterpreted s) = error $ "SBV.Z3.zero: Unexpected uninterpreted sort: " ++ s + cont rm skolemMap = intercalate "\n" $ concatMap extract skolemMap where -- In the skolemMap: -- * Left's are universals: i.e., the model should be true for -- any of these. So, we simply "echo 0" for these values. -- * Right's are existentials. If there are no dependencies (empty list), then we can -- simply use get-value to extract it's value. Otherwise, we have to apply it to -- an appropriate number of 0's to get the final value. - extract (Left s) = ["(echo \"((" ++ show s ++ " " ++ zero (kindOf s) ++ "))\")"] + extract (Left s) = ["(echo \"((" ++ show s ++ " " ++ zero rm (kindOf s) ++ "))\")"] extract (Right (s, [])) = let g = "(get-value (" ++ show s ++ "))" in getVal (kindOf s) g - extract (Right (s, ss)) = let g = "(get-value ((" ++ show s ++ concat [' ' : zero (kindOf a) | a <- ss] ++ ")))" in getVal (kindOf s) g - getVal KReal g = ["(set-option :pp.decimal false)", g, "(set-option :pp.decimal true)", g] + extract (Right (s, ss)) = let g = "(get-value ((" ++ show s ++ concat [' ' : zero rm (kindOf a) | a <- ss] ++ ")))" in getVal (kindOf s) g + getVal KReal g = ["(set-option :pp.decimal false) " ++ g, "(set-option :pp.decimal true) " ++ g] getVal _ g = [g] addTimeOut Nothing o = o addTimeOut (Just i) o diff --git a/sbv/Data/SBV/SMT/SMT.hs b/sbv/Data/SBV/SMT/SMT.hs index 210b182e..3df244c3 100644 --- a/sbv/Data/SBV/SMT/SMT.hs +++ b/sbv/Data/SBV/SMT/SMT.hs @@ -16,88 +16,23 @@ module Data.SBV.SMT.SMT where import qualified Control.Exception as C import Control.Concurrent (newEmptyMVar, takeMVar, putMVar, forkIO) -import Control.DeepSeq (NFData(..)) import Control.Monad (when, zipWithM) import Data.Char (isSpace) import Data.Int (Int8, Int16, Int32, Int64) import Data.List (intercalate, isPrefixOf, isInfixOf) -import Data.Maybe (isNothing, fromJust) import Data.Word (Word8, Word16, Word32, Word64) import System.Directory (findExecutable) -import System.Process (readProcessWithExitCode, runInteractiveProcess, waitForProcess) +import System.Process (runInteractiveProcess, waitForProcess, terminateProcess) import System.Exit (ExitCode(..)) import System.IO (hClose, hFlush, hPutStr, hGetContents, hGetLine) +import qualified Data.Map as M + import Data.SBV.BitVectors.AlgReals import Data.SBV.BitVectors.Data import Data.SBV.BitVectors.PrettyNum import Data.SBV.Utils.TDiff --- | Solver configuration. See also 'z3', 'yices', 'cvc4', and 'boolector, which are instantiations of this type for those solvers, with --- reasonable defaults. In particular, custom configuration can be created by varying those values. (Such as @z3{verbose=True}@.) --- --- Most fields are self explanatory. The notion of precision for printing algebraic reals stems from the fact that such values does --- not necessarily have finite decimal representations, and hence we have to stop printing at some depth. It is important to --- emphasize that such values always have infinite precision internally. The issue is merely with how we print such an infinite --- precision value on the screen. The field 'printRealPrec' controls the printing precision, by specifying the number of digits after --- the decimal point. The default value is 16, but it can be set to any positive integer. --- --- When printing, SBV will add the suffix @...@ at the and of a real-value, if the given bound is not sufficient to represent the real-value --- exactly. Otherwise, the number will be written out in standard decimal notation. Note that SBV will always print the whole value if it --- is precise (i.e., if it fits in a finite number of digits), regardless of the precision limit. The limit only applies if the representation --- of the real value is not finite, i.e., if it is not rational. -data SMTConfig = SMTConfig { - verbose :: Bool -- ^ Debug mode - , timing :: Bool -- ^ Print timing information on how long different phases took (construction, solving, etc.) - , timeOut :: Maybe Int -- ^ How much time to give to the solver. (In seconds) - , printBase :: Int -- ^ Print integral literals in this base (2, 8, and 10, and 16 are supported.) - , printRealPrec :: Int -- ^ Print algebraic real values with this precision. (SReal, default: 16) - , solverTweaks :: [String] -- ^ Additional lines of script to give to the solver (user specified) - , satCmd :: String -- ^ Usually "(check-sat)". However, users might tweak it based on solver characteristics. - , smtFile :: Maybe FilePath -- ^ If Just, the generated SMT script will be put in this file (for debugging purposes mostly) - , useSMTLib2 :: Bool -- ^ If True, we'll treat the solver as using SMTLib2 input format. Otherwise, SMTLib1 - , solver :: SMTSolver -- ^ The actual SMT solver. - , roundingMode :: RoundingMode -- ^ Rounding mode to use for floating-point conversions - } - --- | An SMT engine -type SMTEngine = SMTConfig -> Bool -> [(Quantifier, NamedSymVar)] -> [(String, UnintKind)] -> [Either SW (SW, [SW])] -> String -> IO SMTResult - --- | An SMT solver -data SMTSolver = SMTSolver { - name :: String -- ^ Printable name of the solver - , executable :: String -- ^ The path to its executable - , options :: [String] -- ^ Options to provide to the solver - , engine :: SMTEngine -- ^ The solver engine, responsible for interpreting solver output - , xformExitCode :: ExitCode -> ExitCode -- ^ Should we re-interpret exit codes. Most solvers behave rationally, i.e., id will do. Some (like CVC4) don't. - , capabilities :: SolverCapabilities -- ^ Various capabilities of the solver - } - --- | A model, as returned by a solver -data SMTModel = SMTModel { - modelAssocs :: [(String, CW)] - , modelArrays :: [(String, [String])] -- very crude! - , modelUninterps :: [(String, [String])] -- very crude! - } - deriving Show - --- | The result of an SMT solver call. Each constructor is tagged with --- the 'SMTConfig' that created it so that further tools can inspect it --- and build layers of results, if needed. For ordinary uses of the library, --- this type should not be needed, instead use the accessor functions on --- it. (Custom Show instances and model extractors.) -data SMTResult = Unsatisfiable SMTConfig -- ^ Unsatisfiable - | Satisfiable SMTConfig SMTModel -- ^ Satisfiable with model - | Unknown SMTConfig SMTModel -- ^ Prover returned unknown, with a potential (possibly bogus) model - | ProofError SMTConfig [String] -- ^ Prover errored out - | TimeOut SMTConfig -- ^ Computation timed out (see the 'timeout' combinator) - --- | A script, to be passed to the solver. -data SMTScript = SMTScript { - scriptBody :: String -- ^ Initial feed - , scriptModel :: Maybe String -- ^ Optional continuation script, if the result is sat - } - -- | Extract the final configuration from a result resultConfig :: SMTResult -> SMTConfig resultConfig (Unsatisfiable c) = c @@ -106,16 +41,6 @@ resultConfig (Unknown c _) = c resultConfig (ProofError c _) = c resultConfig (TimeOut c) = c -instance NFData SMTResult where - rnf (Unsatisfiable _) = () - rnf (Satisfiable _ xs) = rnf xs `seq` () - rnf (Unknown _ xs) = rnf xs `seq` () - rnf (ProofError _ xs) = rnf xs `seq` () - rnf (TimeOut _) = () - -instance NFData SMTModel where - rnf (SMTModel assocs unints uarrs) = rnf assocs `seq` rnf unints `seq` rnf uarrs `seq` () - -- | A 'prove' call results in a 'ThmResult' newtype ThmResult = ThmResult SMTResult @@ -127,17 +52,19 @@ newtype SatResult = SatResult SMTResult -- we should warn the user about prefix-existentials. newtype AllSatResult = AllSatResult (Bool, [SMTResult]) +-- | User friendly way of printing theorem results instance Show ThmResult where show (ThmResult r) = showSMTResult "Q.E.D." "Unknown" "Unknown. Potential counter-example:\n" "Falsifiable" "Falsifiable. Counter-example:\n" r +-- | User friendly way of printing satisfiablity results instance Show SatResult where show (SatResult r) = showSMTResult "Unsatisfiable" "Unknown" "Unknown. Potential model:\n" "Satisfiable" "Satisfiable. Model:\n" r --- NB. The Show instance of AllSatResults have to be careful in being lazy enough +-- | The Show instance of AllSatResults. Note that we have to be careful in being lazy enough -- as the typical use case is to pull results out as they become available. instance Show AllSatResult where show (AllSatResult (e, xs)) = go (0::Int) xs @@ -178,51 +105,73 @@ genParse :: Integral a => Kind -> [CW] -> Maybe (a, [CW]) genParse k (x@(CW _ (CWInteger i)):r) | kindOf x == k = Just (fromIntegral i, r) genParse _ _ = Nothing --- Base case, that comes in handy if there are no real variables +-- | Base case for 'SatModel' at unit type. Comes in handy if there are no real variables. instance SatModel () where parseCWs xs = return ((), xs) +-- | 'Bool' as extracted from a model instance SatModel Bool where - parseCWs xs = do (x, r) <- genParse (KBounded False 1) xs + parseCWs xs = do (x, r) <- genParse KBool xs return ((x :: Integer) /= 0, r) +-- | 'Word8' as extracted from a model instance SatModel Word8 where parseCWs = genParse (KBounded False 8) +-- | 'Int8' as extracted from a model instance SatModel Int8 where parseCWs = genParse (KBounded True 8) +-- | 'Word16' as extracted from a model instance SatModel Word16 where parseCWs = genParse (KBounded False 16) +-- | 'Int16' as extracted from a model instance SatModel Int16 where parseCWs = genParse (KBounded True 16) +-- | 'Word32' as extracted from a model instance SatModel Word32 where parseCWs = genParse (KBounded False 32) +-- | 'Int32' as extracted from a model instance SatModel Int32 where parseCWs = genParse (KBounded True 32) +-- | 'Word64' as extracted from a model instance SatModel Word64 where parseCWs = genParse (KBounded False 64) +-- | 'Int64' as extracted from a model instance SatModel Int64 where parseCWs = genParse (KBounded True 64) +-- | 'Integer' as extracted from a model instance SatModel Integer where parseCWs = genParse KUnbounded +-- | 'AlgReal' as extracted from a model instance SatModel AlgReal where parseCWs (CW KReal (CWAlgReal i) : r) = Just (i, r) parseCWs _ = Nothing +-- | 'Float' as extracted from a model +instance SatModel Float where + parseCWs (CW KFloat (CWFloat i) : r) = Just (i, r) + parseCWs _ = Nothing + +-- | 'Double' as extracted from a model +instance SatModel Double where + parseCWs (CW KDouble (CWDouble i) : r) = Just (i, r) + parseCWs _ = Nothing + instance SatModel CW where parseCWs (cw : r) = Just (cw, r) parseCWs [] = Nothing --- when reading a list; go as long as we can (maximal-munch) --- note that this never fails.. +-- | A list of values as extracted from a model. When reading a list, we +-- go as long as we can (maximal-munch). Note that this never fails, as +-- we can always return the empty list! instance SatModel a => SatModel [a] where parseCWs [] = Just ([], []) parseCWs xs = case parseCWs xs of @@ -231,31 +180,37 @@ instance SatModel a => SatModel [a] where Nothing -> Just ([], ys) Nothing -> Just ([], xs) +-- | Tuples extracted from a model instance (SatModel a, SatModel b) => SatModel (a, b) where parseCWs as = do (a, bs) <- parseCWs as (b, cs) <- parseCWs bs return ((a, b), cs) +-- | 3-Tuples extracted from a model instance (SatModel a, SatModel b, SatModel c) => SatModel (a, b, c) where parseCWs as = do (a, bs) <- parseCWs as ((b, c), ds) <- parseCWs bs return ((a, b, c), ds) +-- | 4-Tuples extracted from a model instance (SatModel a, SatModel b, SatModel c, SatModel d) => SatModel (a, b, c, d) where parseCWs as = do (a, bs) <- parseCWs as ((b, c, d), es) <- parseCWs bs return ((a, b, c, d), es) +-- | 5-Tuples extracted from a model instance (SatModel a, SatModel b, SatModel c, SatModel d, SatModel e) => SatModel (a, b, c, d, e) where parseCWs as = do (a, bs) <- parseCWs as ((b, c, d, e), fs) <- parseCWs bs return ((a, b, c, d, e), fs) +-- | 6-Tuples extracted from a model instance (SatModel a, SatModel b, SatModel c, SatModel d, SatModel e, SatModel f) => SatModel (a, b, c, d, e, f) where parseCWs as = do (a, bs) <- parseCWs as ((b, c, d, e, f), gs) <- parseCWs bs return ((a, b, c, d, e, f), gs) +-- | 7-Tuples extracted from a model instance (SatModel a, SatModel b, SatModel c, SatModel d, SatModel e, SatModel f, SatModel g) => SatModel (a, b, c, d, e, f, g) where parseCWs as = do (a, bs) <- parseCWs as ((b, c, d, e, f, g), hs) <- parseCWs bs @@ -268,6 +223,19 @@ class Modelable a where -- | Extract a model, the result is a tuple where the first argument (if True) -- indicates whether the model was "probable". (i.e., if the solver returned unknown.) getModel :: SatModel b => a -> Either String (Bool, b) + -- | Extract a model dictionary. Extract a dictionary mapping the variables to + -- their respective values as returned by the SMT solver. Also see `getModelDictionaries`. + getModelDictionary :: a -> M.Map String CW + -- | Extract a model value for a given element. Also see `getModelValues`. + getModelValue :: SymWord b => String -> a -> Maybe b + getModelValue v r = fromCW `fmap` (v `M.lookup` getModelDictionary r) + -- | Extract a representative name for the model value of an uninterpreted kind. + -- This is supposed to correspond to the value as computed internally by the + -- SMT solver; and is unportable from solver to solver. Also see `getModelUninterpretedValues`. + getModelUninterpretedValue :: String -> a -> Maybe String + getModelUninterpretedValue v r = case v `M.lookup` getModelDictionary r of + Just (CW _ (CWUninterpreted s)) -> Just s + _ -> Nothing -- | A simpler variant of 'getModel' to get a model out without the fuss. extractModel :: SatModel b => a -> Maybe b @@ -280,14 +248,31 @@ class Modelable a where extractModels :: SatModel a => AllSatResult -> [a] extractModels (AllSatResult (_, xs)) = [ms | Right (_, ms) <- map getModel xs] +-- | Get dictionaries from an all-sat call. Similar to `getModelDictionary`. +getModelDictionaries :: AllSatResult -> [M.Map String CW] +getModelDictionaries (AllSatResult (_, xs)) = map getModelDictionary xs + +-- | Extract value of a variable from an all-sat call. Similar to `getModelValue`. +getModelValues :: SymWord b => String -> AllSatResult -> [Maybe b] +getModelValues s (AllSatResult (_, xs)) = map (s `getModelValue`) xs + +-- | Extract value of an uninterpreted variable from an all-sat call. Similar to `getModelUninterpretedValue`. +getModelUninterpretedValues :: String -> AllSatResult -> [Maybe String] +getModelUninterpretedValues s (AllSatResult (_, xs)) = map (s `getModelUninterpretedValue`) xs + +-- | 'ThmResult' as a generic model provider instance Modelable ThmResult where - getModel (ThmResult r) = getModel r - modelExists (ThmResult r) = modelExists r + getModel (ThmResult r) = getModel r + modelExists (ThmResult r) = modelExists r + getModelDictionary (ThmResult r) = getModelDictionary r +-- | 'SatResult' as a generic model provider instance Modelable SatResult where - getModel (SatResult r) = getModel r - modelExists (SatResult r) = modelExists r + getModel (SatResult r) = getModel r + modelExists (SatResult r) = modelExists r + getModelDictionary (SatResult r) = getModelDictionary r +-- | 'SMTResult' as a generic model provider instance Modelable SMTResult where getModel (Unsatisfiable _) = Left "SBV.getModel: Unsatisfiable result" getModel (Unknown _ m) = Right (True, parseModelOut m) @@ -297,6 +282,11 @@ instance Modelable SMTResult where modelExists (Satisfiable{}) = True modelExists (Unknown{}) = False -- don't risk it modelExists _ = False + getModelDictionary (Unsatisfiable _) = M.empty + getModelDictionary (Unknown _ m) = M.fromList (modelAssocs m) + getModelDictionary (ProofError _ _) = M.empty + getModelDictionary (TimeOut _) = M.empty + getModelDictionary (Satisfiable _ m) = M.fromList (modelAssocs m) -- | Extract a model out, will throw error if parsing is unsuccessful parseModelOut :: SatModel a => SMTModel -> a @@ -357,8 +347,9 @@ shUA (f, cases) = (" -- array: " ++ f) : map shC cases where shC s = " " ++ s -- | Helper function to spin off to an SMT solver. -pipeProcess :: SMTConfig -> String -> String -> [String] -> SMTScript -> (String -> String) -> IO (Either String [String]) -pipeProcess cfg nm execName opts script cleanErrs = do +pipeProcess :: SMTConfig -> String -> [String] -> SMTScript -> (String -> String) -> IO (Either String [String]) +pipeProcess cfg execName opts script cleanErrs = do + let nm = show (name (solver cfg)) mbExecPath <- findExecutable execName case mbExecPath of Nothing -> return $ Left $ "Unable to locate executable for " ++ nm @@ -397,13 +388,13 @@ standardSolver config script cleanErrs failure success = do exec = executable smtSolver opts = options smtSolver isTiming = timing config - nmSolver = name smtSolver + nmSolver = show (name smtSolver) msg $ "Calling: " ++ show (unwords (exec:opts)) case smtFile config of Nothing -> return () - Just f -> do putStrLn $ "** Saving the generated script in file: " ++ show f + Just f -> do msg $ "Saving the generated script in file: " ++ show f writeFile f (scriptBody script) - contents <- timeIf isTiming nmSolver $ pipeProcess config nmSolver exec opts script cleanErrs + contents <- timeIf isTiming nmSolver $ pipeProcess config exec opts script cleanErrs msg $ nmSolver ++ " output:\n" ++ either id (intercalate "\n") contents case contents of Left e -> return $ failure (lines e) @@ -413,41 +404,46 @@ standardSolver config script cleanErrs failure success = do -- and can speak SMT-Lib2 (just a little). runSolver :: SMTConfig -> FilePath -> [String] -> SMTScript -> IO (ExitCode, String, String) runSolver cfg execPath opts script - | isNothing $ scriptModel script - = let checkCmd | useSMTLib2 cfg = '\n' : satCmd cfg - | True = "" - in readProcessWithExitCode execPath opts (scriptBody script ++ checkCmd) - | True - = do (send, ask, cleanUp) <- do + = do (send, ask, cleanUp, pid) <- do (inh, outh, errh, pid) <- runInteractiveProcess execPath opts Nothing Nothing let send l = hPutStr inh (l ++ "\n") >> hFlush inh - recv = hGetLine outh `C.catch` (\(_ :: C.SomeException) -> return "") + recv = hGetLine outh ask l = send l >> recv - cleanUp r = do outMVar <- newEmptyMVar - out <- hGetContents outh - _ <- forkIO $ C.evaluate (length out) >> putMVar outMVar () - err <- hGetContents errh - _ <- forkIO $ C.evaluate (length err) >> putMVar outMVar () - hClose inh - takeMVar outMVar - takeMVar outMVar - hClose outh - hClose errh - ex <- waitForProcess pid - -- if the status is unknown, prepare for the possibility of not having a model - -- TBD: This is rather crude and potentially Z3 specific - return $ if "unknown" `isPrefixOf` r && "error" `isInfixOf` (out ++ err) - then (ExitSuccess, r , "") - else (ex, r ++ "\n" ++ out, err) - return (send, ask, cleanUp) - mapM_ send (lines (scriptBody script)) - r <- ask $ satCmd cfg - when (any (`isPrefixOf` r) ["sat", "unknown"]) $ do - let mls = lines (fromJust (scriptModel script)) - when (verbose cfg) $ do putStrLn "** Sending the following model extraction commands:" - mapM_ putStrLn mls - mapM_ send mls - cleanUp r + cleanUp response + = do hClose inh + outMVar <- newEmptyMVar + out <- hGetContents outh + _ <- forkIO $ C.evaluate (length out) >> putMVar outMVar () + err <- hGetContents errh + _ <- forkIO $ C.evaluate (length err) >> putMVar outMVar () + takeMVar outMVar + takeMVar outMVar + hClose outh + hClose errh + ex <- waitForProcess pid + return $ case response of + Nothing -> (ex, out, err) + Just (r, vals) -> -- if the status is unknown, prepare for the possibility of not having a model + -- TBD: This is rather crude and potentially Z3 specific + let finalOut = intercalate "\n" (r : vals) + in if "unknown" `isPrefixOf` r && "error" `isInfixOf` (out ++ err) + then (ExitSuccess, finalOut , "") + else (ex, finalOut ++ "\n" ++ out, err) + return (send, ask, cleanUp, pid) + let executeSolver = do mapM_ send (lines (scriptBody script)) + response <- case scriptModel script of + Nothing -> do send $ satCmd cfg + return Nothing + Just ls -> do r <- ask $ satCmd cfg + vals <- if any (`isPrefixOf` r) ["sat", "unknown"] + then do let mls = lines ls + when (verbose cfg) $ do putStrLn "** Sending the following model extraction commands:" + mapM_ putStrLn mls + mapM ask mls + else return [] + return $ Just (r, vals) + cleanUp response + executeSolver `C.onException` terminateProcess pid -- | In case the SMT-Lib solver returns a response over multiple lines, compress them so we have -- each S-Expression spanning only a single line. We'll ignore things line parentheses inside quotes diff --git a/sbv/Data/SBV/SMT/SMTLib.hs b/sbv/Data/SBV/SMT/SMTLib.hs index f91c26ab..5e3e4913 100644 --- a/sbv/Data/SBV/SMT/SMTLib.hs +++ b/sbv/Data/SBV/SMT/SMTLib.hs @@ -14,7 +14,6 @@ module Data.SBV.SMT.SMTLib(SMTLibPgm, SMTLibConverter, toSMTLib1, toSMTLib2, add import Data.Char (isDigit) import Data.SBV.BitVectors.Data -import Data.SBV.SMT.SMT import Data.SBV.Provers.SExpr import qualified Data.SBV.SMT.SMTLib1 as SMT1 import qualified Data.SBV.SMT.SMTLib2 as SMT2 @@ -23,6 +22,7 @@ import qualified Data.Set as Set (Set, member, toList) -- | An instance of SMT-Lib converter; instantiated for SMT-Lib v1 and v2. (And potentially for newer versions in the future.) type SMTLibConverter = RoundingMode -- ^ User selected rounding mode to be used for floating point arithmetic + -> Maybe Logic -- ^ User selected logic to use. If Nothing, pick automatically. -> SolverCapabilities -- ^ Capabilities of the backend solver targeted -> Set.Set Kind -- ^ Kinds used in the problem -> Bool -- ^ is this a sat problem? @@ -45,7 +45,7 @@ toSMTLib1 :: SMTLibConverter -- | Convert to SMTLib-2 format toSMTLib2 :: SMTLibConverter (toSMTLib1, toSMTLib2) = (cvt SMTLib1, cvt SMTLib2) - where cvt v roundMode solverCaps kindInfo isSat comments qinps skolemMap consts tbls arrs uis axs asgnsSeq cstrs out + where cvt v roundMode smtLogic solverCaps kindInfo isSat comments qinps skolemMap consts tbls arrs uis axs asgnsSeq cstrs out | KUnbounded `Set.member` kindInfo && not (supportsUnboundedInts solverCaps) = unsupported "unbounded integers" | KReal `Set.member` kindInfo && not (supportsReals solverCaps) @@ -64,7 +64,7 @@ toSMTLib2 :: SMTLibConverter unsupported w = error $ "SBV: Given problem needs " ++ w ++ ", which is not supported by SBV for the chosen solver: " ++ capSolverName solverCaps aliasTable = map (\(_, (x, y)) -> (y, x)) qinps converter = if v == SMTLib1 then SMT1.cvt else SMT2.cvt - (pre, post) = converter roundMode solverCaps kindInfo isSat comments qinps skolemMap consts tbls arrs uis axs asgnsSeq cstrs out + (pre, post) = converter roundMode smtLogic solverCaps kindInfo isSat comments qinps skolemMap consts tbls arrs uis axs asgnsSeq cstrs out needsFloats = KFloat `Set.member` kindInfo needsDoubles = KDouble `Set.member` kindInfo needsQuantifiers diff --git a/sbv/Data/SBV/SMT/SMTLib1.hs b/sbv/Data/SBV/SMT/SMTLib1.hs index 633514e0..b372eafc 100644 --- a/sbv/Data/SBV/SMT/SMTLib1.hs +++ b/sbv/Data/SBV/SMT/SMTLib1.hs @@ -42,6 +42,7 @@ nonEq (s, c) = "(not (= " ++ s ++ " " ++ cvtCW c ++ "))" -- | Translate a problem into an SMTLib1 script cvt :: RoundingMode -- ^ User selected rounding mode to be used for floating point arithmetic + -> Maybe Logic -- ^ SMT-Lib logic, if requested by the user -> SolverCapabilities -- ^ capabilities of the current solver -> Set.Set Kind -- ^ kinds used -> Bool -- ^ is this a sat problem? @@ -57,8 +58,9 @@ cvt :: RoundingMode -- ^ User selected rounding mode to be used -> [SW] -- ^ extra constraints -> SW -- ^ output variable -> ([String], [String]) -cvt _roundingMode _solverCaps _kindInfo isSat comments qinps _skolemInps consts tbls arrs uis axs asgnsSeq cstrs out = (pre, post) +cvt _roundingMode smtLogic _solverCaps _kindInfo isSat comments qinps _skolemInps consts tbls arrs uis axs asgnsSeq cstrs out = (pre, post) where logic + | Just l <- smtLogic = show l | null tbls && null arrs && null uis = "QF_BV" | True = "QF_AUFBV" inps = map (fst . snd) qinps @@ -152,7 +154,7 @@ cvtCnst (s, c) = " :assumption (= " ++ show s ++ " " ++ cvtCW c ++ ")" -- no need to worry about Int/Real here as we don't support them with the SMTLib1 interface.. cvtCW :: CW -> String -cvtCW (CW (KBounded False 1) (CWInteger v)) = if v == 0 then "false" else "true" +cvtCW (CW KBool (CWInteger v)) = if v == 0 then "false" else "true" cvtCW x@(CW _ (CWInteger v)) | not (hasSign x) = "bv" ++ show v ++ "[" ++ show (intSizeOf x) ++ "]" -- signed numbers (with 2's complement representation) is problematic -- since there's no way to put a bvneg over a positive number to get minBound.. @@ -261,7 +263,7 @@ cvtType (SBVType []) = error "SBV.SMT.SMTLib1.cvtType: internal: received an emp cvtType (SBVType xs) = unwords $ map kindType xs kindType :: Kind -> String -kindType (KBounded False 1) = "Bool" +kindType KBool = "Bool" kindType (KBounded _ s) = "BitVec[" ++ show s ++ "]" kindType KUnbounded = die "unbounded Integer" kindType KReal = die "real value" diff --git a/sbv/Data/SBV/SMT/SMTLib2.hs b/sbv/Data/SBV/SMT/SMTLib2.hs index b9239886..ec28aca2 100644 --- a/sbv/Data/SBV/SMT/SMTLib2.hs +++ b/sbv/Data/SBV/SMT/SMTLib2.hs @@ -12,13 +12,15 @@ module Data.SBV.SMT.SMTLib2(cvt, addNonEqConstraints) where -import Data.Bits (bit) -import Data.Char (intToDigit) +import Data.Bits (bit) +import Data.Char (intToDigit) +import Data.Function (on) +import Data.Ord (comparing) import qualified Data.Foldable as F (toList) import qualified Data.Map as M import qualified Data.IntMap as IM import qualified Data.Set as Set -import Data.List (intercalate, partition) +import Data.List (intercalate, partition, groupBy, sortBy) import Numeric (showIntAtBase, showHex) import Data.SBV.BitVectors.AlgReals @@ -36,7 +38,7 @@ addNonEqConstraints rm qinps allNonEqConstraints (SMTLibPgm _ (aliasTable, pre, | True = Just $ intercalate "\n" $ pre ++ [ "; --- refuted-models ---" ] - ++ concatMap (nonEqs rm) (map (map intName) nonEqConstraints) + ++ refutedModel ++ post where refutedModel = concatMap (nonEqs rm) (map (map intName) nonEqConstraints) intName (s, c) @@ -47,11 +49,27 @@ addNonEqConstraints rm qinps allNonEqConstraints (SMTLibPgm _ (aliasTable, pre, topUnivs = [s | (_, (_, s)) <- takeWhile (\p -> fst p == EX) qinps] nonEqs :: RoundingMode -> [(String, CW)] -> [String] -nonEqs _ [] = [] -nonEqs rm [sc] = ["(assert " ++ nonEq rm sc ++ ")"] -nonEqs rm (sc:r) = ["(assert (or " ++ nonEq rm sc] - ++ map ((" " ++) . nonEq rm) r - ++ [" ))"] +nonEqs rm scs = format $ interp ps ++ disallow (map eqClass uninterpClasses) + where (ups, ps) = partition (isUninterpreted . snd) scs + format [] = [] + format [m] = ["(assert " ++ m ++ ")"] + format (m:ms) = ["(assert (or " ++ m] + ++ map (" " ++) ms + ++ [" ))"] + -- Regular (or interpreted) sorts simply get a constraint that we disallow the current assignment + interp = map $ nonEq rm + -- Determine the equivalnce classes of uninterpreted sorts: + uninterpClasses = filter (\l -> length l > 1) -- Only need this class if it has at least two members + . map (map fst) -- throw away sorts, we only need the names + . groupBy ((==) `on` snd) -- make sure they belong to the same sort and have the same value + . sortBy (comparing snd) -- sort them according to their sorts first + $ ups -- take the uninterpreted sorts + -- Uninterpreted sorts get a constraint that says the equivalence classes as determined by the solver are disallowed: + eqClass :: [String] -> String + eqClass [] = error "SBV.allSat.nonEqs: Impossible happened, disallow received an empty list" + eqClass cs = "(= " ++ unwords cs ++ ")" + -- Now, take the conjunction of equivalence classes and assert it's negation: + disallow = map $ \ec -> "(not " ++ ec ++ ")" nonEq :: RoundingMode -> (String, CW) -> String nonEq rm (s, c) = "(not (= " ++ s ++ " " ++ cvtCW rm c ++ "))" @@ -61,6 +79,7 @@ tbd e = error $ "SBV.SMTLib2: Not-yet-supported: " ++ e -- | Translate a problem into an SMTLib2 script cvt :: RoundingMode -- ^ User selected rounding mode to be used for floating point arithmetic + -> Maybe Logic -- ^ SMT-Lib logic, if requested by the user -> SolverCapabilities -- ^ capabilities of the current solver -> Set.Set Kind -- ^ kinds used -> Bool -- ^ is this a sat problem? @@ -76,7 +95,7 @@ cvt :: RoundingMode -- ^ User selected rounding mode to be used -> [SW] -- ^ extra constraints -> SW -- ^ output variable -> ([String], [String]) -cvt rm solverCaps kindInfo isSat comments inputs skolemInps consts tbls arrs uis axs (SBVPgm asgnsSeq) cstrs out = (pre, []) +cvt rm smtLogic solverCaps kindInfo isSat comments inputs skolemInps consts tbls arrs uis axs (SBVPgm asgnsSeq) cstrs out = (pre, []) where -- the logic is an over-approaximation hasInteger = KUnbounded `Set.member` kindInfo hasReal = KReal `Set.member` kindInfo @@ -85,6 +104,8 @@ cvt rm solverCaps kindInfo isSat comments inputs skolemInps consts tbls arrs uis hasBVs = not $ null [() | KBounded{} <- Set.toList kindInfo] sorts = [s | KUninterpreted s <- Set.toList kindInfo] logic + | Just l <- smtLogic + = ["(set-logic " ++ show l ++ ") ; NB. User specified."] | hasDouble || hasFloat -- NB. We don't check for quantifiers here, we probably should.. = if hasBVs then ["(set-logic QF_FPABV)"] @@ -244,7 +265,7 @@ swFunType :: [SW] -> SW -> String swFunType ss s = "(" ++ unwords (map swType ss) ++ ") " ++ swType s smtType :: Kind -> String -smtType (KBounded False 1) = "Bool" +smtType KBool = "Bool" smtType (KBounded _ sz) = "(_ BitVec " ++ show sz ++ ")" smtType KUnbounded = "Int" smtType KReal = "Real" @@ -267,14 +288,16 @@ cvtSW skolemMap s | True = show s --- SMTLib2 requires the width of literals to match the type exactly; --- hexadecimal works only for sizes that are multiples of 4. +-- Carefully code hex numbers, SMTLib is picky about lengths of hex constants. For the time +-- being, SBV only supports sizes that are multiples of 4, but the below code is more robust +-- in case of future extensions to support arbitrary sizes. hex :: Int -> Integer -> String +hex 1 v = "#b" ++ show v hex sz v | sz `mod` 4 == 0 = "#x" ++ pad (sz `div` 4) (showHex v "") - | otherwise = "#b" ++ pad sz (showBin v "") - where pad n s = replicate (n - length s) '0' ++ s - showBin = showIntAtBase 2 intToDigit + | True = "#b" ++ pad sz (showBin v "") + where pad n s = replicate (n - length s) '0' ++ s + showBin = showIntAtBase 2 intToDigit cvtCW :: RoundingMode -> CW -> String cvtCW rm x @@ -318,7 +341,8 @@ cvtExp rm skolemMap tableMap expr@(SBVApp _ arguments) = sh expr boolOp = all isBoolean arguments bad | intOp = error $ "SBV.SMTLib2: Unsupported operation on unbounded integers: " ++ show expr | True = error $ "SBV.SMTLib2: Unsupported operation on real values: " ++ show expr - ensureBV = bvOp || bad + ensureBVOrBool = bvOp || boolOp || bad + ensureBV = bvOp || bad addRM s = s ++ " " ++ smtRoundingMode rm lift2 o _ [x, y] = "(" ++ o ++ " " ++ x ++ " " ++ y ++ ")" lift2 o _ sbvs = error $ "SBV.SMTLib2.sh.lift2: Unexpected arguments: " ++ show (o, sbvs) @@ -351,6 +375,7 @@ cvtExp rm skolemMap tableMap expr@(SBVApp _ arguments) = sh expr | needsCheck = "(ite " ++ cond ++ ssw e ++ " " ++ lkUp ++ ")" | True = lkUp where needsCheck = case aKnd of + KBool -> (2::Integer) > fromIntegral l KBounded _ n -> (2::Integer)^n > fromIntegral l KUnbounded -> True KReal -> error "SBV.SMT.SMTLib2.cvtExp: unexpected real valued index" @@ -362,6 +387,7 @@ cvtExp rm skolemMap tableMap expr@(SBVApp _ arguments) = sh expr | hasSign i = "(or " ++ le0 ++ " " ++ gtl ++ ") " | True = gtl ++ " " (less, leq) = case aKnd of + KBool -> error "SBV.SMT.SMTLib2.cvtExp: unexpected boolean valued index" KBounded{} -> if hasSign i then ("bvslt", "bvsle") else ("bvult", "bvule") KUnbounded -> ("<", "<=") KReal -> ("<", "<=") @@ -402,7 +428,7 @@ cvtExp rm skolemMap tableMap expr@(SBVApp _ arguments) = sh expr | intOp = "(div " ++ ssw a ++ " " ++ show (bit i :: Integer) ++ ")" -- Implement shiftR by division by 2^i | True = bad sh (SBVApp op args) - | Just f <- lookup op smtBVOpTable, ensureBV + | Just f <- lookup op smtBVOpTable, ensureBVOrBool = f (any hasSign args) (map ssw args) where -- The first 4 operators below do make sense for Integer's in Haskell, but there's -- no obvious counterpart for them in the SMTLib translation. @@ -446,6 +472,7 @@ cvtExp rm skolemMap tableMap expr@(SBVApp _ arguments) = sh expr , (Rem, lift2 "mod") ] smtOpFloatDoubleTable = smtIntRealShared + ++ [(Quot, lift2WM "/")] smtIntRealShared = [ (Plus, lift2WM "+") , (Minus, lift2WM "-") , (Times, lift2WM "*") diff --git a/sbv/Data/SBV/Tools/ExpectedValue.hs b/sbv/Data/SBV/Tools/ExpectedValue.hs index 09386eb3..5ac23077 100644 --- a/sbv/Data/SBV/Tools/ExpectedValue.hs +++ b/sbv/Data/SBV/Tools/ExpectedValue.hs @@ -22,13 +22,13 @@ import Data.SBV.BitVectors.Data -- warm-up count and the convergence factor. Maximum iteration count can also -- be specified, at which point convergence won't be sought. The boolean controls verbosity. expectedValueWith :: Outputtable a => Bool -> Int -> Maybe Int -> Double -> Symbolic a -> IO [Double] -expectedValueWith verbose warmupCount mbMaxIter epsilon m +expectedValueWith chatty warmupCount mbMaxIter epsilon m | warmupCount < 0 || epsilon < 0 = error $ "SBV.expectedValue: warmup count and epsilon both must be non-negative, received: " ++ show (warmupCount, epsilon) | True = warmup warmupCount (repeat 0) >>= go warmupCount - where progress s | not verbose = return () - | True = putStr $ "\r*** " ++ s + where progress s | not chatty = return () + | True = putStr $ "\r*** " ++ s warmup :: Int -> [Integer] -> IO [Integer] warmup 0 v = do progress $ "Warmup complete, performed " ++ show warmupCount ++ " rounds.\n" return v @@ -42,7 +42,7 @@ expectedValueWith verbose warmupCount mbMaxIter epsilon m let cval o = case o `lookup` cs of Nothing -> error "SBV.expectedValue: Cannot compute expected-values in the presence of uninterpreted constants!" Just cw -> case (cwKind cw, cwVal cw) of - (KBounded False 1, _) -> if cwToBool cw then 1 else 0 + (KBool, _) -> if cwToBool cw then 1 else 0 (KBounded{}, CWInteger v) -> v (KUnbounded, CWInteger v) -> v (KReal, _) -> error "Cannot compute expected-values for real valued results." diff --git a/sbv/Data/SBV/Tools/GenTest.hs b/sbv/Data/SBV/Tools/GenTest.hs index b9d041b8..aec9e142 100644 --- a/sbv/Data/SBV/Tools/GenTest.hs +++ b/sbv/Data/SBV/Tools/GenTest.hs @@ -111,7 +111,7 @@ haskell vname vs = intercalate "\n" $ [ "-- Automatically generated by SBV. Do n valOf [x] = s x valOf xs = "[" ++ intercalate ", " (map s xs) ++ "]" t cw = case kindOf cw of - KBounded False 1 -> "Bool" + KBool -> "Bool" KBounded False 8 -> "Word8" KBounded False 16 -> "Word16" KBounded False 32 -> "Word32" @@ -127,7 +127,7 @@ haskell vname vs = intercalate "\n" $ [ "-- Automatically generated by SBV. Do n KUninterpreted us -> error $ "SBV.renderTest: Unsupported uninterpreted sort: " ++ us _ -> error $ "SBV.renderTest: Unexpected CW: " ++ show cw s cw = case cwKind cw of - KBounded False 1 -> take 5 (show (cwToBool cw) ++ repeat ' ') + KBool -> take 5 (show (cwToBool cw) ++ repeat ' ') KBounded sgn sz -> let CWInteger w = cwVal cw in shex False True (sgn, sz) w KUnbounded -> let CWInteger w = cwVal cw in shexI False True w KFloat -> let CWFloat w = cwVal cw in showHFloat w @@ -198,7 +198,7 @@ c n vs = intercalate "\n" $ ] where mkField p cw i = " " ++ t ++ " " ++ p ++ show i ++ ";" where t = case cwKind cw of - KBounded False 1 -> "SBool" + KBool -> "SBool" KBounded False 8 -> "SWord8" KBounded False 16 -> "SWord16" KBounded False 32 -> "SWord32" @@ -215,7 +215,7 @@ c n vs = intercalate "\n" $ _ -> error $ "SBV.renderTest: Unexpected CW: " ++ show cw mkLine (is, os) = "{{" ++ intercalate ", " (map v is) ++ "}, {" ++ intercalate ", " (map v os) ++ "}}" v cw = case cwKind cw of - KBounded False 1 -> if cwToBool cw then "true " else "false" + KBool -> if cwToBool cw then "true " else "false" KBounded sgn sz -> let CWInteger w = cwVal cw in shex False True (sgn, sz) w KUnbounded -> let CWInteger w = cwVal cw in shexI False True w KFloat -> let CWFloat w = cwVal cw in showCFloat w @@ -231,11 +231,11 @@ c n vs = intercalate "\n" $ inp cw i = mkBool cw (n ++ "[i].input.i" ++ show i) out cw i = mkBool cw (n ++ "[i].output.o" ++ show i) mkBool cw s = case cwKind cw of - KBounded False 1 -> "(" ++ s ++ " == true) ? \"true \" : \"false\"" - _ -> s + KBool -> "(" ++ s ++ " == true) ? \"true \" : \"false\"" + _ -> s fmtString = unwords (map fmt is) ++ " -> " ++ unwords (map fmt os) fmt cw = case cwKind cw of - KBounded False 1 -> "%s" + KBool -> "%s" KBounded False 8 -> "0x%02\"PRIx8\"" KBounded False 16 -> "0x%04\"PRIx16\"U" KBounded False 32 -> "0x%08\"PRIx32\"UL" @@ -267,7 +267,7 @@ forte vname bigEndian ss vs = intercalate "\n" $ [ "// Automatically generated b toF True = '1' toF False = '0' blast cw = case cwKind cw of - KBounded False 1 -> [toF (cwToBool cw)] + KBool -> [toF (cwToBool cw)] KBounded False 8 -> xlt 8 (cwVal cw) KBounded False 16 -> xlt 16 (cwVal cw) KBounded False 32 -> xlt 32 (cwVal cw) diff --git a/sbv/Data/SBV/Tools/Optimize.hs b/sbv/Data/SBV/Tools/Optimize.hs index 9b3e026b..00f76c31 100644 --- a/sbv/Data/SBV/Tools/Optimize.hs +++ b/sbv/Data/SBV/Tools/Optimize.hs @@ -19,7 +19,7 @@ import Data.Maybe (fromJust) import Data.SBV.BitVectors.Data import Data.SBV.BitVectors.Model (OrdSymbolic(..), EqSymbolic(..)) import Data.SBV.Provers.Prover (satWith, defaultSMTCfg) -import Data.SBV.SMT.SMT (SatModel, getModel, SMTConfig(..)) +import Data.SBV.SMT.SMT (SatModel, getModel) import Data.SBV.Utils.Boolean -- | Optimizer configuration. Note that iterative and quantified approaches are in general not interchangeable. diff --git a/sbv/Data/SBV/Tools/Polynomial.hs b/sbv/Data/SBV/Tools/Polynomial.hs index 8db12b29..e22d3719 100644 --- a/sbv/Data/SBV/Tools/Polynomial.hs +++ b/sbv/Data/SBV/Tools/Polynomial.hs @@ -9,12 +9,13 @@ -- Implementation of polynomial arithmetic ----------------------------------------------------------------------------- +{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE TypeSynonymInstances #-} -module Data.SBV.Tools.Polynomial {-(Polynomial(..), crc, crcBV)-} where +module Data.SBV.Tools.Polynomial (Polynomial(..), crc, crcBV, ites, addPoly, mdp) where import Data.Bits (Bits(..)) import Data.List (genericTake) @@ -100,7 +101,11 @@ sp st a | True = foldr (\x y -> sh x ++ " + " ++ y) (sh (last cs)) (init cs) ++ t where t | st = " :: GF(2^" ++ show n ++ ")" | True = "" +#if __GLASGOW_HASKELL__ >= 708 + n = maybe (error "SBV.Polynomial.sp: Unexpected non-finite usage!") id (bitSizeMaybe a) +#else n = bitSize a +#endif is = [n-1, n-2 .. 0] cs = map fst $ filter snd $ zip is (map (testBit a) is) sh 0 = "1" From ec0395e9d0ff8f77da4c600097726aa68d64c848 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 16:01:43 -0700 Subject: [PATCH 19/45] Update regression test number 289, which now runs successfully. This test works as of revision db08bfafa9a67e20fc3ddc5157075cc471ea2b04. Versions of SBV prior to 3.1 did not distinguish the boolean type from the size-1 bitvector type; this example uses literals like "0b0" and "0b1", so it triggered the SBV bug, which manifested as a type error from the external SMT solver. --- tests/issues/issue289.icry.stdout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/issues/issue289.icry.stdout b/tests/issues/issue289.icry.stdout index eccdfb1f..390512b8 100644 --- a/tests/issues/issue289.icry.stdout +++ b/tests/issues/issue289.icry.stdout @@ -2,5 +2,5 @@ Loading module Cryptol Loading module Cryptol Loading module Main Q.E.D. -Q.E.D. +av1 3701879183 3218045100 = False Q.E.D. From ffebd5c33042d94acb8e401a368fad902d7e1802 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 16:02:38 -0700 Subject: [PATCH 20/45] Update output for regression test number 290 --- tests/issues/issue290.icry.stdout | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/issues/issue290.icry.stdout b/tests/issues/issue290.icry.stdout index 6ff2d23e..09bf5382 100644 --- a/tests/issues/issue290.icry.stdout +++ b/tests/issues/issue290.icry.stdout @@ -1,6 +1,5 @@ Loading module Cryptol Loading module Cryptol -Loading module Cryptol Loading module issue290 Loading module issue290bar Assuming a = 2 From c8b3b8c1342aaeef76040fd0f3f9a53c066a2a68 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 16:13:56 -0700 Subject: [PATCH 21/45] Update output for regression test 086. (cf. revision 0ee396d434269e0bee0def34fc3201d38e4a1d22) --- tests/issues/issue086.icry.stdout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/issues/issue086.icry.stdout b/tests/issues/issue086.icry.stdout index ed01454a..b50ecb49 100644 --- a/tests/issues/issue086.icry.stdout +++ b/tests/issues/issue086.icry.stdout @@ -1,2 +1,2 @@ Loading module Cryptol -&& : {a} a -> a -> a +(&&) : {a} a -> a -> a From c2859d579b3b89266f62d4c5192e07c671a6492d Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 21 Jul 2014 16:18:47 -0700 Subject: [PATCH 22/45] Update output for regression test 226 --- tests/issues/issue226.icry.stdout | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/issues/issue226.icry.stdout b/tests/issues/issue226.icry.stdout index 3816a8f2..6a06a394 100644 --- a/tests/issues/issue226.icry.stdout +++ b/tests/issues/issue226.icry.stdout @@ -1,6 +1,5 @@ Loading module Cryptol Loading module Cryptol -Loading module Cryptol Loading module issue226r2 Loading module issue226 Type Synonyms From c5a14e388dedef3cbe74475e2d394d2b3a5ca70b Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 22 Jul 2014 13:44:17 -0700 Subject: [PATCH 23/45] Rewrite symbolic simulator in pure functional style We now take advantage of new features of SBV 3.1, including the "sBranch" mechanism for evaluating reachability of conditional branches. --- src/Cryptol/Symbolic.hs | 315 +++++---------- src/Cryptol/Symbolic/Prims.hs | 700 +++++++++++++--------------------- src/Cryptol/Symbolic/Value.hs | 138 +++---- 3 files changed, 411 insertions(+), 742 deletions(-) diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index df3ea1d3..d2e473ba 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -7,13 +7,14 @@ -- Portability : portable {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE PatternGuards #-} + module Cryptol.Symbolic where import Control.Applicative -import Control.Monad (foldM, liftM2, replicateM, when) -import Control.Monad.Fix (mfix) +import Control.Monad (replicateM, when) import Control.Monad.IO.Class -import Control.Monad.Reader (ask, local) +import Data.List (transpose) import qualified Data.Map as Map import Data.Maybe (fromJust) import Data.Monoid (Monoid(..)) @@ -21,7 +22,7 @@ import Data.Traversable (traverse) import qualified Control.Exception as X import qualified Data.SBV as SBV -import Data.SBV (SBool) +import Data.SBV (SBool, Symbolic) import qualified Cryptol.ModuleSystem as M import qualified Cryptol.ModuleSystem.Env as M @@ -53,19 +54,11 @@ prove (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolve case predArgTypes schema of Left msg -> return (Right (Left msg, modEnv), []) Right ts -> do when verbose $ putStrLn "Simulating..." - let runE = do args <- mapM forallFinType ts - env <- evalDecls emptyEnv extDgs - v <- evalExpr env expr - b <- foldM vApply v args - return (fromVBit b) - let simenv = SimEnv - { simConfig = prover - , simPath = SBV.true - , simIteSolver = useSolverIte - , simVerbose = verbose - } + let env = evalDecls (emptyEnv useSolverIte) extDgs + let v = evalExpr env expr result <- SBV.proveWith prover $ do - b <- runTheMonad runE simenv + args <- mapM forallFinType ts + b <- return $! fromVBit (foldl vApply v args) when verbose $ liftIO $ putStrLn $ "Calling " ++ proverName ++ "..." return b let solution = case result of @@ -83,19 +76,11 @@ sat (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolverI case predArgTypes schema of Left msg -> return (Right (Left msg, modEnv), []) Right ts -> do when verbose $ putStrLn "Simulating..." - let runE = do args <- mapM existsFinType ts - env <- evalDecls emptyEnv extDgs - v <- evalExpr env expr - b <- foldM vApply v args - return (fromVBit b) - let simenv = SimEnv - { simConfig = prover - , simPath = SBV.true - , simIteSolver = useSolverIte - , simVerbose = verbose - } + let env = evalDecls (emptyEnv useSolverIte) extDgs + let v = evalExpr env expr result <- SBV.satWith prover $ do - b <- runTheMonad runE simenv + args <- mapM existsFinType ts + b <- return $! fromVBit (foldl vApply v args) when verbose $ liftIO $ putStrLn $ "Calling " ++ proverName ++ "..." return b let solution = case result of @@ -177,59 +162,56 @@ predArgTypes schema@(Forall ts ps ty) go (TUser _ _ t) = go t go _ = Nothing -forallFinType :: FinType -> TheMonad Value +forallFinType :: FinType -> Symbolic Value forallFinType ty = case ty of - FTBit -> VBit <$> liftSymbolic SBV.forall_ + FTBit -> VBit <$> SBV.forall_ FTSeq 0 FTBit -> return $ VWord (SBV.literal (bv 0 0)) - FTSeq n FTBit -> VWord <$> liftSymbolic (forallBV_ n) - FTSeq n t -> vSeq <$> replicateM n (mkThunk t) - FTTuple ts -> VTuple <$> mapM mkThunk ts - FTRecord fs -> VRecord <$> mapM (traverseSnd mkThunk) fs - where - mkThunk :: FinType -> TheMonad Thunk - mkThunk t = Ready <$> forallFinType t + FTSeq n FTBit -> VWord <$> (forallBV_ n) + FTSeq n t -> vSeq <$> replicateM n (forallFinType t) + FTTuple ts -> VTuple <$> mapM forallFinType ts + FTRecord fs -> VRecord <$> mapM (traverseSnd forallFinType) fs -existsFinType :: FinType -> TheMonad Value +existsFinType :: FinType -> Symbolic Value existsFinType ty = case ty of - FTBit -> VBit <$> liftSymbolic SBV.exists_ + FTBit -> VBit <$> SBV.exists_ FTSeq 0 FTBit -> return $ VWord (SBV.literal (bv 0 0)) - FTSeq n FTBit -> VWord <$> liftSymbolic (existsBV_ n) - FTSeq n t -> vSeq <$> replicateM n (mkThunk t) - FTTuple ts -> VTuple <$> mapM mkThunk ts - FTRecord fs -> VRecord <$> mapM (traverseSnd mkThunk) fs - where - mkThunk :: FinType -> TheMonad Thunk - mkThunk t = Ready <$> existsFinType t + FTSeq n FTBit -> VWord <$> existsBV_ n + FTSeq n t -> vSeq <$> replicateM n (existsFinType t) + FTTuple ts -> VTuple <$> mapM existsFinType ts + FTRecord fs -> VRecord <$> mapM (traverseSnd existsFinType) fs -- Simulation environment ------------------------------------------------------ data Env = Env - { envVars :: Map.Map QName Thunk + { envVars :: Map.Map QName Value , envTypes :: Map.Map TVar TValue + , envIteSolver :: Bool } instance Monoid Env where mempty = Env { envVars = Map.empty , envTypes = Map.empty + , envIteSolver = False } mappend l r = Env { envVars = Map.union (envVars l) (envVars r) , envTypes = Map.union (envTypes l) (envTypes r) + , envIteSolver = envIteSolver l || envIteSolver r } -emptyEnv :: Env -emptyEnv = Env Map.empty Map.empty +emptyEnv :: Bool -> Env +emptyEnv useIteSolver = Env Map.empty Map.empty useIteSolver -- | Bind a variable in the evaluation environment. -bindVar :: (QName, Thunk) -> Env -> Env +bindVar :: (QName, Value) -> Env -> Env bindVar (n, thunk) env = env { envVars = Map.insert n thunk (envVars env) } -- | Lookup a variable in the environment. -lookupVar :: QName -> Env -> Maybe Thunk +lookupVar :: QName -> Env -> Maybe Value lookupVar n env = Map.lookup n (envVars env) -- | Bind a type variable of kind *. @@ -240,229 +222,118 @@ bindType p ty env = env { envTypes = Map.insert p ty (envTypes env) } lookupType :: TVar -> Env -> Maybe TValue lookupType p env = Map.lookup p (envTypes env) --- Path conditions ------------------------------------------------------------- - -thenBranch :: SBool -> TheMonad a -> TheMonad a -thenBranch s = local $ \e -> e { simPath = (SBV.&&&) s (simPath e) } - -elseBranch :: SBool -> TheMonad a -> TheMonad a -elseBranch s = thenBranch (SBV.bnot s) - -isFeasible :: SBool -> TheMonad Bool -isFeasible path - | path `SBV.isConcretely` (== False) = return False - | path `SBV.isConcretely` (== True) = return True - | otherwise = do - useSolverIte <- simIteSolver <$> ask - verbose <- simVerbose <$> ask - config <- simConfig <$> ask - if useSolverIte then do - when verbose $ liftIO $ putStrLn "Testing branch condition with solver..." - res <- liftSymbolic $ SBV.internalIsTheoremWith config (Just 5) (SBV.bnot path) - case res of - Just isThm -> do let msg = if isThm then "Infeasible." else "Feasible." - when verbose $ liftIO $ putStrLn msg - return (not isThm) - Nothing -> do when verbose $ liftIO $ putStrLn "Undetermined." - return True - else return True - -evalIf :: SBool -> TheMonad Value -> TheMonad Value -> TheMonad Value -evalIf s m1 m2 = do - path <- simPath <$> ask - let path1 = (SBV.&&&) path s - let path2 = (SBV.&&&) path (SBV.bnot s) - c1 <- isFeasible path1 - if not c1 - then elseBranch s m2 - else do - c2 <- isFeasible path2 - if not c2 - then thenBranch s m1 - else do - v1 <- thenBranch s m1 - v2 <- elseBranch s m2 - ite s v1 v2 - -- Expressions ----------------------------------------------------------------- -evalExpr :: Env -> Expr -> TheMonad Value +evalExpr :: Env -> Expr -> Value evalExpr env expr = case expr of - ECon econ -> return $ evalECon econ - EList es _ty -> vSeq <$> traverse eval' es - ETuple es -> VTuple <$> traverse eval' es - ERec fields -> VRecord <$> traverse (traverseSnd eval') fields - ESel e sel -> evalSel sel =<< eval e - EIf b e1 e2 -> do - VBit s <- evalExpr env b - evalIf s (evalExpr env e1) (evalExpr env e2) - + ECon econ -> evalECon econ + EList es _ty -> vSeq (map eval es) + ETuple es -> VTuple (map eval es) + ERec fields -> VRecord [ (f, eval e) | (f, e) <- fields ] + ESel e sel -> evalSel sel (eval e) + EIf b e1 e2 -> evalIf (fromVBit (eval b)) (eval e1) (eval e2) + where evalIf = if envIteSolver env then SBV.sBranch else SBV.ite EComp ty e mss -> evalComp env (evalType env ty) e mss - EVar n -> force $ fromJust (lookupVar n env) + EVar n -> fromJust (lookupVar n env) -- TODO: how to deal with uninterpreted functions? - ETAbs tv e -> return $ VPoly $ \ty -> evalExpr (bindType (tpVar tv) ty env) e - ETApp e ty -> do VPoly f <- eval e - f (evalType env ty) - EApp e1 e2 -> do VFun f <- eval e1 - f =<< eval' e2 - EAbs n _ty e -> return $ VFun $ \th -> evalExpr (bindVar (n, th) env) e + ETAbs tv e -> VPoly $ \ty -> evalExpr (bindType (tpVar tv) ty env) e + ETApp e ty -> fromVPoly (eval e) (evalType env ty) + EApp e1 e2 -> fromVFun (eval e1) (eval e2) + EAbs n _ty e -> VFun $ \x -> evalExpr (bindVar (n, x) env) e EProofAbs _prop e -> eval e EProofApp e -> eval e ECast e _ty -> eval e - EWhere e ds -> do env' <- evalDecls env ds - evalExpr env' e + EWhere e ds -> evalExpr (evalDecls env ds) e where eval e = evalExpr env e - eval' e = delay (evalExpr env e) evalType :: Env -> Type -> TValue evalType env ty = Cryptol.Eval.Type.evalType env' ty where env' = Cryptol.Eval.Env.EvalEnv Map.empty (envTypes env) -evalSel :: Selector -> Value -> TheMonad Value +evalSel :: Selector -> Value -> Value evalSel sel v = case sel of TupleSel n _ -> case v of - VTuple xs -> force $ xs !! (n - 1) -- 1-based indexing - VNil -> return VNil + VTuple xs -> xs !! (n - 1) -- 1-based indexing + VNil -> VNil VCons {} -> liftList v - VFun f -> return $ VFun $ \x -> evalSel sel =<< f x + VFun f -> VFun (\x -> evalSel sel (f x)) RecordSel n _ -> case v of - VRecord bs -> force $ fromJust (lookup n bs) - VNil -> return VNil + VRecord bs -> fromJust (lookup n bs) + VNil -> VNil VCons {} -> liftList v - VFun f -> return $ VFun $ \x -> evalSel sel =<< f x + VFun f -> VFun $ \x -> evalSel sel (f x) ListSel n _ -> case v of --VSeq xs -> force $ xs !! n -- 0-based indexing - VWord s -> return $ VBit (SBV.sbvTestBit s n) - _ -> let go :: Int -> Value -> TheMonad Thunk - go 0 (VCons x _) = return x - go i (VCons _ y) = go (i - 1) =<< force y + VWord s -> VBit (SBV.sbvTestBit s n) + _ -> let go :: Int -> Value -> Value + go 0 (VCons x _) = x + go i (VCons _ y) = go (i - 1) y go _ _ = error "internal error" - in force =<< go n v + in go n v where - liftList VNil = return VNil - liftList (VCons x xs) = do x' <- delay (evalSel sel =<< force x) - xs' <- delay (liftList =<< force xs) - return (VCons x' xs') + liftList VNil = VNil + liftList (VCons x xs) = VCons (evalSel sel x) (liftList xs) liftList _ = panic "Cryptol.Symbolic.evalSel" ["Malformed list, while lifting selector"] -- Declarations ---------------------------------------------------------------- -evalDecls :: Env -> [DeclGroup] -> TheMonad Env -evalDecls = foldM evalDeclGroup +evalDecls :: Env -> [DeclGroup] -> Env +evalDecls = foldl evalDeclGroup -evalDeclGroup :: Env -> DeclGroup -> TheMonad Env +evalDeclGroup :: Env -> DeclGroup -> Env evalDeclGroup env dg = case dg of - NonRecursive d -> do binding <- evalDecl env d - return $ bindVar binding env - Recursive ds -> mfix $ \env' -> do - bindings <- traverse (evalDecl env') ds - return $ foldr bindVar env bindings + NonRecursive d -> bindVar (evalDecl env d) env + Recursive ds -> let env' = foldr bindVar env bindings + bindings = map (evalDecl env') ds + in env' -evalDecl :: Env -> Decl -> TheMonad (QName, Thunk) -evalDecl env d = do - thunk <- delay $ evalExpr env (dDefinition d) - return (dName d, thunk) +evalDecl :: Env -> Decl -> (QName, Value) +evalDecl env d = (dName d, evalExpr env (dDefinition d)) -- List Comprehensions --------------------------------------------------------- -data LazySeq a = LSNil | LSCons a (MLazySeq a) -type MLazySeq a = TheMonad (LazySeq a) +evalComp :: Env -> TValue -> Expr -> [[Match]] -> Value +evalComp env seqty body ms + | Just (_len, _el) <- isTSeq seqty = vSeq [ evalExpr e body | e <- envs ] + | otherwise = evalPanic "Cryptol.Eval" [ "evalComp given a non sequence" + , show seqty + ] -instance Functor LazySeq where - fmap _ LSNil = LSNil - fmap f (LSCons x xs) = LSCons (f x) (fmap f <$> xs) + -- XXX we could potentially print this as a number if the type was available. + where + -- generate a new environment for each iteration of each parallel branch + benvs = map (branchEnvs env) ms -singleLS :: a -> LazySeq a -singleLS x = LSCons x (return LSNil) + -- take parallel slices of each environment. when the length of the list + -- drops below the number of branches, one branch has terminated. + allBranches es = length es == length ms + slices = takeWhile allBranches (transpose benvs) -zipWithLS :: (a -> b -> c) -> LazySeq a -> LazySeq b -> LazySeq c -zipWithLS f (LSCons x xs) (LSCons y ys) = LSCons (f x y) (liftM2 (zipWithLS f) xs ys) -zipWithLS _ _ _ = LSNil - -repeatLS :: a -> LazySeq a -repeatLS x = xs where xs = LSCons x (return xs) - -transposeLS :: [LazySeq a] -> LazySeq [a] -transposeLS = foldr (zipWithLS (:)) (repeatLS []) - -appendLS :: LazySeq a -> LazySeq a -> LazySeq a -appendLS LSNil ys = ys -appendLS (LSCons x xs') ys = - LSCons x $ do - xs <- xs' - return (appendLS xs ys) - -appendMLS :: MLazySeq a -> MLazySeq a -> MLazySeq a -appendMLS xs ys = do - l <- xs - case l of - LSNil -> ys - LSCons x xs' -> return (LSCons x (appendMLS xs' ys)) - -bindMLS :: MLazySeq a -> (a -> MLazySeq b) -> MLazySeq b -bindMLS xs k = do - l <- xs - case l of - LSNil -> return LSNil - LSCons x xs' -> appendMLS (k x) (bindMLS xs' k) - -toLazySeq :: Value -> LazySeq Thunk -toLazySeq (VCons th1 th2) = LSCons th1 (toLazySeq <$> force th2) -toLazySeq VNil = LSNil -toLazySeq (VWord w) = go (SBV.bitSize w - 1) - where go i | i < 0 = LSNil - | otherwise = LSCons (Ready (VBit (SBV.sbvTestBit w i))) (return (go (i - 1))) -toLazySeq _ = error "toLazySeq" - -toSeq :: LazySeq Thunk -> TheMonad Value -toSeq LSNil = return VNil -toSeq (LSCons x xs) = VCons x <$> delay (toSeq =<< xs) - -evalComp :: Env -> TValue -> Expr -> [[Match]] -> TheMonad Value -evalComp env seqty body ms = - case isTSeq seqty of - Nothing -> evalPanic "Cryptol.Eval" [ "evalComp given a non sequence", show seqty ] - Just (_len, _el) -> do - -- generate a new environment for each iteration of each parallel branch - (benvs :: [LazySeq Env]) <- mapM (branchEnvs env) ms - -- take parallel slices of each environment. - let slices :: LazySeq [Env] - slices = transposeLS benvs - - -- join environments to produce environments at each step through the process. - let envs :: LazySeq Env - envs = fmap mconcat slices - - thunks <- bindMLS (return envs) (\e -> singleLS <$> delay (evalExpr e body)) - toSeq thunks + -- join environments to produce environments at each step through the process. + envs = map mconcat slices -- | Turn a list of matches into the final environments for each iteration of -- the branch. -branchEnvs :: Env -> [Match] -> MLazySeq Env +branchEnvs :: Env -> [Match] -> [Env] branchEnvs env matches = case matches of - [] -> return (singleLS env) - m : ms -> bindMLS (evalMatch env m) (\env' -> branchEnvs env' ms) + [] -> [env] + m : ms -> do env' <- evalMatch env m + branchEnvs env' ms -- | Turn a match into the list of environments it represents. -evalMatch :: Env -> Match -> MLazySeq Env +evalMatch :: Env -> Match -> [Env] evalMatch env m = case m of - - From n _ty expr -> do - ths <- toLazySeq <$> evalExpr env expr - return $ fmap (\th -> bindVar (n, th) env) ths - - Let d -> do - binding <- evalDecl env d - return $ singleLS (bindVar binding env) + From n _ty expr -> [ bindVar (n, v) env | v <- fromSeq (evalExpr env expr) ] + Let d -> [ bindVar (evalDecl env d) env ] diff --git a/src/Cryptol/Symbolic/Prims.hs b/src/Cryptol/Symbolic/Prims.hs index eaccffbe..be2f102e 100644 --- a/src/Cryptol/Symbolic/Prims.hs +++ b/src/Cryptol/Symbolic/Prims.hs @@ -11,9 +11,7 @@ module Cryptol.Symbolic.Prims where import Control.Applicative -import Control.Monad (liftM, zipWithM) import Data.Bits -import Data.Traversable import Cryptol.Eval.Value (TValue, numTValue, finTValue, isTSeq, isTBit, isTFun, isTTuple, isTRec) import Cryptol.Prims.Syntax (ECon(..)) @@ -29,61 +27,6 @@ import qualified Data.SBV.Tools.Polynomial as Poly traverseSnd :: Functor f => (a -> f b) -> (t, a) -> f (t, b) traverseSnd f (x, y) = (,) x <$> f y --- Symbolic if-then-else ------------------------------------------------------- - -ite :: SBool -> Value -> Value -> TheMonad Value -ite s v1 v2 - | Just b <- SBV.unliteral s = return $ if b then v1 else v2 - | otherwise = mergeValue s v1 v2 - -iteM :: SBool -> TheMonad Value -> TheMonad Value -> TheMonad Value -iteM s m1 m2 - | Just b <- SBV.unliteral s = if b then m1 else m2 - | otherwise = do v1 <- m1 - v2 <- m2 - mergeValue s v1 v2 - -iteThunk :: SBool -> Thunk -> Thunk -> TheMonad Thunk -iteThunk s th1 th2 - | Just b <- SBV.unliteral s = return $ if b then th1 else th2 - | otherwise = mergeThunk s th1 th2 - -mergeValue :: SBool -> Value -> Value -> TheMonad Value -mergeValue c v1 v2 = - case (v1, v2) of - (VRecord fs1, VRecord fs2) -> VRecord <$> zipWithM mergeField fs1 fs2 - (VTuple ts1 , VTuple ts2 ) -> VTuple <$> zipWithM (mergeThunk c) ts1 ts2 - (VBit b1 , VBit b2 ) -> return $ VBit $ SBV.symbolicMerge c b1 b2 - (VWord w1 , VWord w2 ) -> return $ VWord $ SBV.symbolicMerge c w1 w2 - (VNil , VNil ) -> return $ VNil - (VCons h1 t1, VCons h2 t2) -> VCons <$> mergeThunk c h1 h2 <*> mergeThunk c t1 t2 - (VFun f1 , VFun f2 ) -> return $ VFun $ \th -> do - y1 <- f1 th - y2 <- f2 th - mergeValue c y1 y2 - (VPoly f1 , VPoly f2 ) -> return $ VPoly $ \ty -> do - y1 <- f1 ty - y2 <- f2 ty - mergeValue c y1 y2 - (VWord w1 , _ ) -> do w2 <- fromWord v2 - return $ VWord $ SBV.symbolicMerge c w1 w2 - (_ , VWord w2 ) -> do w1 <- fromWord v1 - return $ VWord $ SBV.symbolicMerge c w1 w2 - (_ , _ ) -> fail "merge: shape mismatch" - where - mergeField (n1, th1) (n2, th2) - | n1 == n2 = (,) n1 <$> mergeThunk c th1 th2 - | otherwise = error "merge: shape mismatch" --- ^ FIXME: allow merging of VWord/VSeq. - -mergeThunk :: SBool -> Thunk -> Thunk -> TheMonad Thunk -mergeThunk c (Ready v1) (Ready v2) = Ready <$> mergeValue c v1 v2 -mergeThunk c thunk1 thunk2 = - delay $ do - v1 <- force thunk1 - v2 <- force thunk2 - mergeValue c v1 v2 - -- Primitives ------------------------------------------------------------------ -- See also Cryptol.Prims.Eval.evalECon @@ -115,39 +58,29 @@ evalECon econ = -- (f === g) x = (f x == g x) tlam $ \_ -> tlam $ \b -> - VFun $ \th1 -> return $ - VFun $ \th2 -> return $ - VFun $ \th3 -> do - VFun f <- force th1 - VFun g <- force th2 - x <- f th3 - y <- g th3 - cmpBinary cmpEq cmpEq SBV.true b x y + VFun $ \f -> + VFun $ \g -> + VFun $ \x -> cmpBinary cmpEq cmpEq SBV.true b (vApply f x) (vApply g x) ECFunNotEq -> -- {a b} (Cmp b) => (a -> b) -> (a -> b) -> a -> Bit -- (f !== g) x = (f x != g x) tlam $ \_ -> tlam $ \b -> - VFun $ \th1 -> return $ - VFun $ \th2 -> return $ - VFun $ \th3 -> do - VFun f <- force th1 - VFun g <- force th2 - x <- f th3 - y <- g th3 - cmpBinary cmpNotEq cmpNotEq SBV.false b x y + VFun $ \f -> + VFun $ \g -> + VFun $ \x -> cmpBinary cmpNotEq cmpNotEq SBV.false b (vApply f x) (vApply g x) ECMin -> -- {a} (Cmp a) => a -> a -> a -- min x y = if x <= y then x else y - binary $ \a x y -> do - c <- cmpBinary cmpLtEq cmpLtEq SBV.false a x y - ite (fromVBit c) x y + binary $ \a x y -> + let c = cmpBinary cmpLtEq cmpLtEq SBV.false a x y + in SBV.ite (fromVBit c) x y ECMax -> -- {a} (Cmp a) => a -> a -> a -- max x y = if y <= x then x else y - binary $ \a x y -> do - c <- cmpBinary cmpLtEq cmpLtEq SBV.false a y x - ite (fromVBit c) x y + binary $ \a x y -> + let c = cmpBinary cmpLtEq cmpLtEq SBV.false a y x + in SBV.ite (fromVBit c) x y ECAnd -> binary (logicBinary (SBV.&&&) (.&.)) ECOr -> binary (logicBinary (SBV.|||) (.|.)) @@ -159,377 +92,319 @@ evalECon econ = tlam $ \m -> tlam $ \_ -> tlam $ \a -> - VFun $ \xs -> return $ - VFun $ \th -> do - v <- force xs - case v of - VWord x -> do - i <- fromWord =<< force th - return $ VWord (SBV.sbvShiftLeft x i) - _ -> do - z <- Ready <$> zeroV a - let shl :: Integer -> TheMonad Value - shl i = - case numTValue m of - Inf -> dropV i xs - Nat j | i >= j -> replicateV j z - | otherwise -> do ys <- delay (dropV i xs) - zs <- delay (replicateV i z) - catV ys zs - selectV shl th + VFun $ \xs -> + VFun $ \y -> + case xs of + VWord x -> VWord (SBV.sbvShiftLeft x (fromWord y)) + _ -> selectV shl y + where + shl :: Integer -> Value + shl i = + case numTValue m of + Inf -> dropV i xs + Nat j | i >= j -> replicateV j (zeroV a) + | otherwise -> catV (dropV i xs) (replicateV i (zeroV a)) ECShiftR -> -- {m,n,a} (fin n) => [m] a -> [n] -> [m] a tlam $ \m -> tlam $ \_ -> tlam $ \a -> - VFun $ \xs -> return $ - VFun $ \th -> do - v <- force xs - case v of - VWord x -> do - i <- fromWord =<< force th - return $ VWord (SBV.sbvShiftRight x i) - _ -> do - z <- Ready <$> zeroV a - let shr :: Integer -> TheMonad Value - shr i = - case numTValue m of - Inf -> do zs <- delay (replicateV i z) - catV zs xs - Nat j | i >= j -> replicateV j z - | otherwise -> do zs <- delay (replicateV i z) - ys <- delay (takeV (j - i) xs) - catV zs ys - selectV shr th + VFun $ \xs -> + VFun $ \y -> + case xs of + VWord x -> VWord (SBV.sbvShiftRight x (fromWord y)) + _ -> selectV shr y + where + shr :: Integer -> Value + shr i = + case numTValue m of + Inf -> catV (replicateV i (zeroV a)) xs + Nat j | i >= j -> replicateV j (zeroV a) + | otherwise -> catV (replicateV i (zeroV a)) (takeV (j - i) xs) ECRotL -> -- {m,n,a} (fin m, fin n) => [m] a -> [n] -> [m] a tlam $ \m -> tlam $ \_ -> tlam $ \_ -> - VFun $ \xs -> return $ - VFun $ \th -> do - v <- force xs - case v of - VWord x -> do - i <- fromWord =<< force th - return $ VWord (SBV.sbvRotateLeft x i) - _ -> do - let rol :: Integer -> TheMonad Value - rol i = do - let k = i `mod` finTValue m - ys <- delay (dropV k xs) - zs <- delay (takeV k xs) - catV ys zs - selectV rol th + VFun $ \xs -> + VFun $ \y -> + case xs of + VWord x -> VWord (SBV.sbvRotateLeft x (fromWord y)) + _ -> selectV rol y + where + rol :: Integer -> Value + rol i = catV (dropV k xs) (takeV k xs) + where k = i `mod` finTValue m ECRotR -> -- {m,n,a} (fin m, fin n) => [m] a -> [n] -> [m] a tlam $ \m -> tlam $ \_ -> tlam $ \_ -> - VFun $ \xs -> return $ - VFun $ \th -> do - v <- force xs - case v of - VWord x -> do - i <- fromWord =<< force th - return $ VWord (SBV.sbvRotateRight x i) - _ -> do - let ror :: Integer -> TheMonad Value - ror i = do - let k = (- i) `mod` finTValue m - ys <- delay (dropV k xs) - zs <- delay (takeV k xs) - catV ys zs - selectV ror th + VFun $ \xs -> + VFun $ \y -> + case xs of + VWord x -> VWord (SBV.sbvRotateRight x (fromWord y)) + _ -> selectV ror y + where + ror :: Integer -> Value + ror i = catV (dropV k xs) (takeV k xs) + where k = (- i) `mod` finTValue m ECCat -> -- {a,b,d} (fin a) => [a] d -> [b] d -> [a + b] d tlam $ \_ -> tlam $ \_ -> tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> catV th1 th2 + VFun $ \v1 -> + VFun $ \v2 -> catV v1 v2 ECSplitAt -> -- {a,b,c} (fin a) => [a+b] c -> ([a]c,[b]c) tlam $ \(finTValue -> a) -> tlam $ \_ -> tlam $ \_ -> - VFun $ \th -> do - xs <- delay (takeV a th) - ys <- delay (dropV a th) - return $ VTuple [xs, ys] + VFun $ \v -> VTuple [takeV a v, dropV a v] ECJoin -> -- {a,b,c} (fin b) => [a][b]c -> [a * b]c tlam $ \a -> tlam $ \_ -> tlam $ \c -> - VFun $ \th -> do - let go :: Thunk -> TheMonad Value - go xss = do - v <- force xss - case v of - VNil -> return VNil - VCons xs xss' -> catV xs =<< delay (go xss') - _ -> error "join" - if not (numTValue a == Inf && isTBit c) then go th else do + VFun $ \v -> + if not (numTValue a == Inf && isTBit c) then go v else go (mapV (vSeq . fromSeq) v) -- avoid concatenating infinitely many VWords - let blast x = do - v <- force x - bits <- fromSeq v - return $ Ready (vSeq bits) - go =<< delay (mapV blast th) + where + go :: Value -> Value + go xss = do + case xss of + VNil -> VNil + VCons xs xss' -> catV xs (go xss') + _ -> error "join" ECSplit -> -- {a,b,c} (fin b) => [a * b] c -> [a][b] c tlam $ \(numTValue -> a) -> tlam $ \(finTValue -> b) -> tlam $ \_ -> - VFun $ \th -> do - let dec (Nat 0) = Nothing - dec (Nat n) = Just (Nat (n - 1)) - dec Inf = Just Inf - let go :: Nat' -> Thunk -> TheMonad Value + VFun $ \v -> + let go :: Nat' -> Value -> Value go t xs = case dec t of - Nothing -> return VNil - Just t' -> do - ys <- delay (takeV b xs) - zs <- delay (dropV b xs) - VCons ys <$> delay (go t' zs) - go a th + Nothing -> VNil + Just t' -> VCons (takeV b xs) (go t' (dropV b xs)) + in go a v ECReverse -> -- {a,b} (fin a) => [a] b -> [a] b tlam $ \_ -> tlam $ \_ -> - VFun $ \th -> do - v <- force th - ths <- fromSeq v - return $ vSeq (reverse ths) + VFun $ \v -> vSeq (reverse (fromSeq v)) ECTranspose -> -- {a,b,c} [a][b]c -> [b][a]c tlam $ \_a -> tlam $ \b -> tlam $ \_c -> - VFun $ \th -> do - let hd :: Thunk -> TheMonad Thunk - hd thunk = (fst . fromVCons) <$> force thunk - let tl :: Thunk -> TheMonad Thunk - tl thunk = (snd . fromVCons) <$> force thunk - let dec (Nat 0) = Nothing - dec (Nat n) = Just (Nat (n - 1)) - dec Inf = Just Inf - let transp :: Nat' -> Thunk -> TheMonad Value - transp t xss = - case dec t of - Nothing -> return VNil - Just t' -> do - ys <- delay (mapV hd xss) - yss <- delay (mapV tl xss) - VCons ys <$> delay (transp t' yss) - transp (numTValue b) th + VFun $ \v -> transp (numTValue b) v + where + hd :: Value -> Value + hd = fst . fromVCons + tl :: Value -> Value + tl = snd . fromVCons + transp :: Nat' -> Value -> Value + transp t xss = + case dec t of + Nothing -> VNil + Just t' -> VCons (mapV hd xss) (transp t' (mapV tl xss)) ECAt -> -- {n,a,i} (fin i) => [n]a -> [i] -> a tlam $ \_ -> tlam $ \a -> tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - err <- Ready <$> zeroV a -- default for out-of-bounds accesses - selectV (\i -> nthV err th1 i) th2 + VFun $ \xs -> + VFun $ \y -> + let err = zeroV a -- default for out-of-bounds accesses + in selectV (\i -> nthV err xs i) y ECAtRange -> -- {n,a,m,i} (fin i) => [n]a -> [m][i] -> [m]a tlam $ \_ -> tlam $ \a -> tlam $ \_ -> tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - err <- Ready <$> zeroV a -- default for out-of-bounds accesses - let f :: Thunk -> TheMonad Thunk - f th = delay (selectV (\i -> nthV err th1 i) th) - mapV f th2 + VFun $ \xs -> + VFun $ \ys -> + let err = zeroV a -- default for out-of-bounds accesses + in mapV (selectV (\i -> nthV err xs i)) ys ECAtBack -> -- {n,a,i} (fin n, fin i) => [n]a -> [i] -> a tlam $ \(finTValue -> n) -> tlam $ \a -> tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - err <- Ready <$> zeroV a -- default for out-of-bounds accesses - selectV (\i -> nthV err th1 (n - 1 - i)) th2 + VFun $ \xs -> + VFun $ \y -> + let err = zeroV a -- default for out-of-bounds accesses + in selectV (\i -> nthV err xs (n - 1 - i)) y ECAtRangeBack -> -- {n,a,m,i} (fin n, fin i) => [n]a -> [m][i] -> [m]a tlam $ \(finTValue -> n) -> tlam $ \a -> tlam $ \_ -> tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - err <- Ready <$> zeroV a -- default for out-of-bounds accesses - let f :: Thunk -> TheMonad Thunk - f th = delay (selectV (\i -> nthV err th1 (n - 1 - i)) th) - mapV f th2 + VFun $ \xs -> + VFun $ \ys -> + let err = zeroV a -- default for out-of-bounds accesses + in mapV (selectV (\i -> nthV err xs (n - 1 - i))) ys ECFromThen -> -- {first, next, bits, len} (...) => [len][bits] tlam $ \first -> tlam $ \next -> tlam $ \bits -> - VPoly $ \len -> do + VPoly $ \len -> let w = fromInteger (finTValue bits) - let delta = finTValue next - finTValue first - let lit i = Ready (VWord (SBV.literal (bv w i))) - let go :: Integer -> Integer -> TheMonad Value - go 0 _ = return VNil - go n i = VCons (lit i) <$> delay (go (n - 1) (i + delta)) - go (finTValue len) (finTValue first) + delta = finTValue next - finTValue first + lit i = VWord (SBV.literal (bv w i)) + go :: Integer -> Integer -> Value + go 0 _ = VNil + go n i = VCons (lit i) (go (n - 1) (i + delta)) + in go (finTValue len) (finTValue first) ECFromTo -> -- {first, last, bits} (...) => [1 + (last - first)][bits] tlam $ \first -> tlam $ \lst -> - VPoly $ \bits -> do + VPoly $ \bits -> let w = fromInteger (finTValue bits) - let lit i = Ready (VWord (SBV.literal (bv w i))) - let go :: Integer -> Integer -> TheMonad Value - go 0 _ = return VNil - go n i = VCons (lit i) <$> delay (go (n - 1) (i + 1)) - go (finTValue lst - finTValue first + 1) (finTValue first) + lit i = VWord (SBV.literal (bv w i)) + go :: Integer -> Integer -> Value + go 0 _ = VNil + go n i = VCons (lit i) (go (n - 1) (i + 1)) + in go (finTValue lst - finTValue first + 1) (finTValue first) ECFromThenTo -> -- {first, next, last, bits, len} (...) => [len][bits] tlam $ \first -> tlam $ \next -> tlam $ \_lst -> tlam $ \bits -> - VPoly $ \len -> do + VPoly $ \len -> let w = fromInteger (finTValue bits) - let delta = finTValue next - finTValue first - let lit i = Ready (VWord (SBV.literal (bv w i))) - let go :: Integer -> Integer -> TheMonad Value - go 0 _ = return VNil - go n i = VCons (lit i) <$> delay (go (n - 1) (i + delta)) - go (finTValue len) (finTValue first) + delta = finTValue next - finTValue first + lit i = VWord (SBV.literal (bv w i)) + go :: Integer -> Integer -> Value + go 0 _ = VNil + go n i = VCons (lit i) (go (n - 1) (i + delta)) + in go (finTValue len) (finTValue first) ECInfFrom -> -- {a} (fin a) => [a] -> [inf][a] - tlam $ \a -> - VFun $ \th -> do - let delta = SBV.literal (bv (fromInteger (finTValue a)) 1) - let from x = VCons (Ready (VWord x)) <$> delay (from (x + delta)) - x0 <- fromWord =<< force th - from x0 + tlam $ \(finTValue -> a) -> + VFun $ \(fromWord -> x0) -> + let delta = SBV.literal (bv (fromInteger a) 1) + from x = VCons (VWord x) (from (x + delta)) + in from x0 ECInfFromThen -> -- {a} (fin a) => [a] -> [a] -> [inf][a] tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - x1 <- fromWord =<< force th1 - x2 <- fromWord =<< force th2 + VFun $ \(fromWord -> x1) -> + VFun $ \(fromWord -> x2) -> let delta = x2 - x1 - let from x = VCons (Ready (VWord x)) <$> delay (from (x + delta)) - from x1 + from x = VCons (VWord x) (from (x + delta)) + in from x1 -- {at,len} (fin len) => [len][8] -> at ECError -> tlam $ \at -> tlam $ \(finTValue -> _len) -> - VFun $ \_msg -> zeroV at -- error/undefined, is arbitrarily translated to 0 + VFun $ \_msg -> zeroV at -- error/undefined, is arbitrarily translated to 0 ECPMul -> -- {a,b} (fin a, fin b) => [a] -> [b] -> [max 1 (a + b) - 1] tlam $ \(finTValue -> i) -> tlam $ \(finTValue -> j) -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do + VFun $ \v1 -> + VFun $ \v2 -> let k = max 1 (i + j) - 1 - let mul _ [] ps = ps + mul _ [] ps = ps mul as (b:bs) ps = mul (SBV.false : as) bs (Poly.ites b (as `Poly.addPoly` ps) ps) - xs <- traverse (fmap fromVBit . force) =<< fromSeq =<< force th1 - ys <- traverse (fmap fromVBit . force) =<< fromSeq =<< force th2 - let zs = take (fromInteger k) (mul xs ys [] ++ repeat SBV.false) - return $ vSeq (map (Ready . VBit) zs) + xs = map fromVBit (fromSeq v1) + ys = map fromVBit (fromSeq v2) + zs = take (fromInteger k) (mul xs ys [] ++ repeat SBV.false) + in vSeq (map VBit zs) ECPDiv -> -- {a,b} (fin a, fin b) => [a] -> [b] -> [a] tlam $ \(finTValue -> i) -> tlam $ \_ -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - xs <- traverse (fmap fromVBit . force) =<< fromSeq =<< force th1 - ys <- traverse (fmap fromVBit . force) =<< fromSeq =<< force th2 - let zs = take (fromInteger i) (fst (Poly.mdp (reverse xs) (reverse ys)) ++ repeat SBV.false) - return $ vSeq (map (Ready . VBit) (reverse zs)) + VFun $ \v1 -> + VFun $ \v2 -> + let xs = map fromVBit (fromSeq v1) + ys = map fromVBit (fromSeq v2) + zs = take (fromInteger i) (fst (Poly.mdp (reverse xs) (reverse ys)) ++ repeat SBV.false) + in vSeq (map VBit (reverse zs)) ECPMod -> -- {a,b} (fin a, fin b) => [a] -> [b+1] -> [b] tlam $ \_ -> tlam $ \(finTValue -> j) -> - VFun $ \th1 -> return $ - VFun $ \th2 -> do - xs <- traverse (fmap fromVBit . force) =<< fromSeq =<< force th1 - ys <- traverse (fmap fromVBit . force) =<< fromSeq =<< force th2 - let zs = take (fromInteger j) (snd (Poly.mdp (reverse xs) (reverse ys)) ++ repeat SBV.false) - return $ vSeq (map (Ready . VBit) (reverse zs)) + VFun $ \v1 -> + VFun $ \v2 -> + let xs = map fromVBit (fromSeq v1) + ys = map fromVBit (fromSeq v2) + zs = take (fromInteger j) (snd (Poly.mdp (reverse xs) (reverse ys)) ++ repeat SBV.false) + in vSeq (map VBit (reverse zs)) ECRandom -> error "ECRandom" -selectV :: (Integer -> TheMonad Value) -> Thunk -> TheMonad Value -selectV f th = do - ths <- fromSeq =<< force th - bits <- map fromVBit <$> traverse force ths -- index bits in big-endian order - let sel :: Integer -> [SBool] -> TheMonad Value - sel offset [] = f offset - sel offset (b : bs) = iteM b m1 m2 - where m1 = sel (offset + 2 ^ length bs) bs - m2 = sel offset bs - sel 0 bits +dec :: Nat' -> Maybe Nat' +dec (Nat 0) = Nothing +dec (Nat n) = Just (Nat (n - 1)) +dec Inf = Just Inf -replicateV :: Integer -> Thunk -> TheMonad Value -replicateV n x = return (go n) +selectV :: (Integer -> Value) -> Value -> Value +selectV f v = sel 0 bits + where + bits = map fromVBit (fromSeq v) -- index bits in big-endian order + + sel :: Integer -> [SBool] -> Value + sel offset [] = f offset + sel offset (b : bs) = SBV.ite b m1 m2 + where m1 = sel (offset + 2 ^ length bs) bs + m2 = sel offset bs + +replicateV :: Integer -> Value -> Value +replicateV n x = go n where go 0 = VNil - go i = VCons x (Ready (go (i - 1))) + go i = VCons x (go (i - 1)) -nthV :: Thunk -> Thunk -> Integer -> TheMonad Value -nthV err xs n = do - v <- force xs - case v of - VCons x xs' | n == 0 -> force x +nthV :: Value -> Value -> Integer -> Value +nthV err xs n = + case xs of + VCons x xs' | n == 0 -> x | otherwise -> nthV err xs' (n - 1) VWord x -> let i = bitSize x - 1 - fromInteger n - in if i < 0 then force err else - return $ VBit (SBV.sbvTestBit x i) - _ -> force err + in if i < 0 then err else + VBit (SBV.sbvTestBit x i) + _ -> err -mapV :: (Thunk -> TheMonad Thunk) -> Thunk -> TheMonad Value -mapV f th = do - v <- force th +mapV :: (Value -> Value) -> Value -> Value +mapV f v = do case v of - VNil -> return VNil - VCons x xs -> do y <- f x - ys <- delay (mapV f xs) - return $ VCons y ys + VNil -> VNil + VCons x xs -> VCons (f x) (mapV f xs) _ -> error "mapV" -catV :: Thunk -> Thunk -> TheMonad Value +catV :: Value -> Value -> Value catV xs ys = do - v <- force xs - case v of - VCons x xs' -> VCons x <$> delay (catV xs' ys) - VNil -> force ys - VWord x -> do y <- fromWord =<< force ys - return $ VWord (cat x y) + case xs of + VCons x xs' -> VCons x (catV xs' ys) + VNil -> ys + VWord x -> VWord (cat x (fromWord ys)) _ -> error "catV" -dropV :: Integer -> Thunk -> TheMonad Value -dropV 0 xs = force xs -dropV n xs = do - v <- force xs - case v of +dropV :: Integer -> Value -> Value +dropV 0 xs = xs +dropV n xs = + case xs of VCons _ xs' -> dropV (n - 1) xs' - VNil -> return VNil - VWord w -> return $ VWord $ extract (bitSize w - 1 - fromInteger n) 0 w + VNil -> VNil + VWord w -> VWord $ extract (bitSize w - 1 - fromInteger n) 0 w _ -> error "dropV" -takeV :: Integer -> Thunk -> TheMonad Value -takeV 0 _ = return VNil -takeV n xs = do - v <- force xs - case v of - VWord w -> return $ VWord $ extract (bitSize w - 1) (bitSize w - fromInteger n) w - VCons x xs' -> VCons x <$> delay (takeV (n - 1) xs') - VNil -> return VNil +takeV :: Integer -> Value -> Value +takeV 0 _ = VNil +takeV n xs = + case xs of + VWord w -> VWord $ extract (bitSize w - 1) (bitSize w - fromInteger n) w + VCons x xs' -> VCons x (takeV (n - 1) xs') + VNil -> VNil _ -> error "takeV" -- | Make a numeric constant. @@ -548,23 +423,18 @@ ecDemoteV = tlam $ \valT -> -- Operation Lifting ----------------------------------------------------------- -type Binary = TValue -> Value -> Value -> TheMonad Value +type Binary = TValue -> Value -> Value -> Value binary :: Binary -> Value binary f = VPoly $ \ty -> - return $ VFun $ \th1 -> - return $ VFun $ \th2 -> do - v1 <- force th1 - v2 <- force th2 - f ty v1 v2 + VFun $ \v1 -> + VFun $ \v2 -> f ty v1 v2 -type Unary = TValue -> Value -> TheMonad Value +type Unary = TValue -> Value -> Value unary :: Unary -> Value unary f = VPoly $ \ty -> - return $ VFun $ \th -> do - v <- force th - f ty v + VFun $ \v -> f ty v -- Type Values ----------------------------------------------------------------- @@ -594,48 +464,37 @@ toTypeVal ty arithBinary :: (SWord -> SWord -> SWord) -> Binary arithBinary op = loop . toTypeVal where - loop' ty th1 th2 = delay $ do - v1 <- force th1 - v2 <- force th2 - loop ty v1 v2 loop ty l r = case ty of TVBit -> evalPanic "arithBinop" ["Invalid arguments"] - TVSeq _ TVBit -> VWord <$> (op <$> fromWord l <*> fromWord r) - TVSeq 0 _ -> return VNil - TVSeq n t -> VCons <$> loop' t hl hr <*> loop' (TVSeq (n - 1) t) tl tr + TVSeq _ TVBit -> VWord (op (fromWord l) (fromWord r)) + TVSeq 0 _ -> VNil + TVSeq n t -> VCons (loop t hl hr) (loop (TVSeq (n - 1) t) tl tr) where (hl, tl) = fromVCons l (hr, tr) = fromVCons r - TVStream t -> VCons <$> loop' t hl hr <*> loop' ty tl tr + TVStream t -> VCons (loop t hl hr) (loop ty tl tr) where (hl, tl) = fromVCons l (hr, tr) = fromVCons r - TVTuple ts -> VTuple <$> sequenceA (zipWith3 loop' ts (fromVTuple l) (fromVTuple r)) - TVRecord fs -> VRecord <$> traverse (traverseSnd id) - [ (f, loop' t (lookupRecord f l) (lookupRecord f r)) - | (f, t) <- fs ] - TVFun _ t -> return $ VFun $ \ x -> do - lx <- fromVFun l x - rx <- fromVFun r x - loop t lx rx + TVTuple ts -> VTuple (zipWith3 loop ts (fromVTuple l) (fromVTuple r)) + TVRecord fs -> VRecord [ (f, loop t (lookupRecord f l) (lookupRecord f r)) | (f, t) <- fs ] + TVFun _ t -> VFun (\x -> loop t (fromVFun l x) (fromVFun r x)) -- | Models functions of type `{a} (Arith a) => a -> a` arithUnary :: (SWord -> SWord) -> Unary arithUnary op = loop . toTypeVal where - loop' ty thunk = delay (loop ty =<< force thunk) loop ty v = case ty of TVBit -> evalPanic "arithUnary" ["Invalid arguments"] - TVSeq _ TVBit -> VWord <$> (op <$> fromWord v) - TVSeq 0 _ -> return VNil - TVSeq n t -> VCons <$> loop' t vh <*> loop' (TVSeq (n - 1) t) vt + TVSeq _ TVBit -> VWord (op (fromWord v)) + TVSeq 0 _ -> VNil + TVSeq n t -> VCons (loop t vh) (loop (TVSeq (n - 1) t) vt) where (vh, vt) = fromVCons v - TVStream t -> VCons <$> loop' t vh <*> loop' ty vt + TVStream t -> VCons (loop t vh) (loop ty vt) where (vh, vt) = fromVCons v - TVTuple ts -> VTuple <$> zipWithM loop' ts (fromVTuple v) - TVRecord fs -> VRecord <$> traverse (traverseSnd id) - [ (f, loop' t (lookupRecord f v)) | (f, t) <- fs ] - TVFun _ t -> return $ VFun $ \ x -> loop t =<< fromVFun v x + TVTuple ts -> VTuple (zipWith loop ts (fromVTuple v)) + TVRecord fs -> VRecord [ (f, loop t (lookupRecord f v)) | (f, t) <- fs ] + TVFun _ t -> VFun (\x -> loop t (fromVFun v x)) sExp :: SWord -> SWord -> SWord sExp x y = go (SBV.blastLE y) @@ -654,131 +513,106 @@ sLg2 x = go 0 -- Cmp ------------------------------------------------------------------------- -cmpValue :: (SBool -> SBool -> TheMonad a -> TheMonad a) - -> (SWord -> SWord -> TheMonad a -> TheMonad a) - -> (Value -> Value -> TheMonad a -> TheMonad a) +cmpValue :: (SBool -> SBool -> a -> a) + -> (SWord -> SWord -> a -> a) + -> (Value -> Value -> a -> a) cmpValue fb fw = cmp where cmp v1 v2 k = case (v1, v2) of - (VRecord fs1, VRecord fs2) -> cmpThunks (map snd fs1) (map snd fs2) k - (VTuple ths1, VTuple ths2) -> cmpThunks ths1 ths2 k + (VRecord fs1, VRecord fs2) -> cmpValues (map snd fs1) (map snd fs2) k + (VTuple vs1 , VTuple vs2 ) -> cmpValues vs1 vs2 k (VBit b1 , VBit b2 ) -> fb b1 b2 k (VWord w1 , VWord w2 ) -> fw w1 w2 k (VNil , VNil ) -> k - (VCons h1 t1, VCons h2 t2) -> cmpThunks [h1,t1] [h2,t2] k + (VCons h1 t1, VCons h2 t2) -> cmpValues [h1,t1] [h2,t2] k (VFun {} , VFun {} ) -> error "Functions are not comparable" (VPoly {} , VPoly {} ) -> error "Polymorphic values are not comparable" - (VWord w1 , _ ) -> do { w2 <- fromWord v2; fw w1 w2 k } - (_ , VWord w2 ) -> do { w1 <- fromWord v1; fw w1 w2 k } + (VWord w1 , _ ) -> fw w1 (fromWord v2) k + (_ , VWord w2 ) -> fw (fromWord v1) w2 k (_ , _ ) -> error "type mismatch" - cmpThunks (x1 : xs1) (x2 : xs2) k = do - v1 <- force x1 - v2 <- force x2 - cmp v1 v2 (cmpThunks xs1 xs2 k) - cmpThunks _ _ k = k -lazyConj :: Monad m => SBool -> m SBool -> m SBool -lazyConj s m - | s `SBV.isConcretely` (== False) = return s - | otherwise = liftM ((SBV.&&&) s) m + cmpValues (x1 : xs1) (x2 : xs2) k = cmp x1 x2 (cmpValues xs1 xs2 k) + cmpValues _ _ k = k -lazyDisj :: Monad m => SBool -> m SBool -> m SBool -lazyDisj s m - | s `SBV.isConcretely` (== True) = return s - | otherwise = liftM ((SBV.|||) s) m +cmpEq :: SBV.EqSymbolic a => a -> a -> SBool -> SBool +cmpEq x y k = (SBV.&&&) ((SBV..==) x y) k -cmpEq :: (SBV.EqSymbolic a, Monad m) => a -> a -> m SBool -> m SBool -cmpEq x y k = lazyConj ((SBV..==) x y) k +cmpNotEq :: SBV.EqSymbolic a => a -> a -> SBool -> SBool +cmpNotEq x y k = (SBV.|||) ((SBV../=) x y) k -cmpNotEq :: (SBV.EqSymbolic a, Monad m) => a -> a -> m SBool -> m SBool -cmpNotEq x y k = lazyDisj ((SBV../=) x y) k +cmpLt, cmpGt :: SBV.OrdSymbolic a => a -> a -> SBool -> SBool +cmpLt x y k = (SBV.|||) ((SBV..<) x y) (cmpEq x y k) +cmpGt x y k = (SBV.|||) ((SBV..>) x y) (cmpEq x y k) -cmpLt, cmpGt :: (SBV.OrdSymbolic a, Monad m) => a -> a -> m SBool -> m SBool -cmpLt x y k = lazyDisj ((SBV..<) x y) (cmpEq x y k) -cmpGt x y k = lazyDisj ((SBV..>) x y) (cmpEq x y k) +cmpLtEq, cmpGtEq :: SBV.OrdSymbolic a => a -> a -> SBool -> SBool +cmpLtEq x y k = (SBV.&&&) ((SBV..<=) x y) (cmpNotEq x y k) +cmpGtEq x y k = (SBV.&&&) ((SBV..>=) x y) (cmpNotEq x y k) -cmpLtEq, cmpGtEq :: (SBV.OrdSymbolic a, Monad m) => a -> a -> m SBool -> m SBool -cmpLtEq x y k = lazyConj ((SBV..<=) x y) (cmpNotEq x y k) -cmpGtEq x y k = lazyConj ((SBV..>=) x y) (cmpNotEq x y k) - -cmpBinary :: (SBool -> SBool -> TheMonad SBool -> TheMonad SBool) - -> (SWord -> SWord -> TheMonad SBool -> TheMonad SBool) +cmpBinary :: (SBool -> SBool -> SBool -> SBool) + -> (SWord -> SWord -> SBool -> SBool) -> SBool -> Binary -cmpBinary fb fw b _ty v1 v2 = liftM VBit $ cmpValue fb fw v1 v2 (return b) +cmpBinary fb fw b _ty v1 v2 = VBit (cmpValue fb fw v1 v2 b) -- Logic ----------------------------------------------------------------------- -errorV :: String -> TValue -> TheMonad Value -errorV msg = return . go . toTypeVal +errorV :: String -> TValue -> Value +errorV msg = go . toTypeVal where go ty = case ty of TVBit -> VBit (error msg) - TVSeq n t -> vSeq (replicate n (Ready (go t))) - TVStream t -> let v = VCons (Ready (go t)) (Ready v) in v - TVTuple ts -> VTuple [ Ready (go t) | t <- ts ] - TVRecord fs -> VRecord [ (n, Ready (go t)) | (n, t) <- fs ] - TVFun _ t -> VFun (\ _ -> return (go t)) + TVSeq n t -> vSeq (replicate n (go t)) + TVStream t -> let v = VCons (go t) v in v + TVTuple ts -> VTuple [ go t | t <- ts ] + TVRecord fs -> VRecord [ (n, go t) | (n, t) <- fs ] + TVFun _ t -> VFun (const (go t)) - - -zeroV :: TValue -> TheMonad Value -zeroV = return . go . toTypeVal +zeroV :: TValue -> Value +zeroV = go . toTypeVal where go ty = case ty of TVBit -> VBit SBV.false TVSeq n TVBit -> VWord (SBV.literal (bv n 0)) - TVSeq n t -> vSeq (replicate n (Ready (go t))) - TVStream t -> let v = VCons (Ready (go t)) (Ready v) in v - TVTuple ts -> VTuple [ Ready (go t) | t <- ts ] - TVRecord fs -> VRecord [ (n, Ready (go t)) | (n, t) <- fs ] - TVFun _ t -> VFun (\ _ -> return (go t)) + TVSeq n t -> vSeq (replicate n (go t)) + TVStream t -> let v = VCons (go t) v in v + TVTuple ts -> VTuple [ go t | t <- ts ] + TVRecord fs -> VRecord [ (n, go t) | (n, t) <- fs ] + TVFun _ t -> VFun (const (go t)) -- | Merge two values given a binop. This is used for and, or and xor. logicBinary :: (SBool -> SBool -> SBool) -> (SWord -> SWord -> SWord) -> Binary logicBinary bop op = loop . toTypeVal where - loop' ty th1 th2 = delay $ do - v1 <- force th1 - v2 <- force th2 - loop ty v1 v2 loop ty l r = case ty of - TVBit -> return $ VBit (bop (fromVBit l) (fromVBit r)) - TVSeq _ TVBit -> VWord <$> (op <$> fromWord l <*> fromWord r) - TVSeq 0 _ -> return VNil - TVSeq n t -> VCons <$> loop' t hl hr <*> loop' (TVSeq (n - 1) t) tl tr + TVBit -> VBit (bop (fromVBit l) (fromVBit r)) + TVSeq _ TVBit -> VWord (op (fromWord l) (fromWord r)) + TVSeq 0 _ -> VNil + TVSeq n t -> VCons (loop t hl hr) (loop (TVSeq (n - 1) t) tl tr) where (hl, tl) = fromVCons l (hr, tr) = fromVCons r - TVStream t -> VCons <$> loop' t hl hr <*> loop' ty tl tr + TVStream t -> VCons (loop t hl hr) (loop ty tl tr) where (hl, tl) = fromVCons l (hr, tr) = fromVCons r - TVTuple ts -> VTuple <$> sequenceA (zipWith3 loop' ts (fromVTuple l) (fromVTuple r)) - TVRecord fs -> VRecord <$> traverse (traverseSnd id) - [ (f, loop' t (lookupRecord f l) (lookupRecord f r)) - | (f, t) <- fs ] - TVFun _ t -> return $ VFun $ \ x -> do - lx <- fromVFun l x - rx <- fromVFun r x - loop t lx rx + TVTuple ts -> VTuple (zipWith3 loop ts (fromVTuple l) (fromVTuple r)) + TVRecord fs -> VRecord [ (f, loop t (lookupRecord f l) (lookupRecord f r)) | (f, t) <- fs ] + TVFun _ t -> VFun (\x -> loop t (fromVFun l x) (fromVFun r x)) logicUnary :: (SBool -> SBool) -> (SWord -> SWord) -> Unary logicUnary bop op = loop . toTypeVal where - loop' ty thunk = delay (loop ty =<< force thunk) loop ty v = case ty of - TVBit -> return $ VBit (bop (fromVBit v)) - TVSeq _ TVBit -> VWord <$> (op <$> fromWord v) - TVSeq 0 _ -> return VNil - TVSeq n t -> VCons <$> loop' t vh <*> loop' (TVSeq (n - 1) t) vt + TVBit -> VBit (bop (fromVBit v)) + TVSeq _ TVBit -> VWord (op (fromWord v)) + TVSeq 0 _ -> VNil + TVSeq n t -> VCons (loop t vh) (loop (TVSeq (n - 1) t) vt) where (vh, vt) = fromVCons v - TVStream t -> VCons <$> loop' t vh <*> loop' ty vt + TVStream t -> VCons (loop t vh) (loop ty vt) where (vh, vt) = fromVCons v - TVTuple ts -> VTuple <$> zipWithM loop' ts (fromVTuple v) - TVRecord fs -> VRecord <$> traverse (traverseSnd id) - [ (f, loop' t (lookupRecord f v)) | (f, t) <- fs ] - TVFun _ t -> return $ VFun $ \ x -> loop t =<< fromVFun v x + TVTuple ts -> VTuple (zipWith loop ts (fromVTuple v)) + TVRecord fs -> VRecord [ (f, loop t (lookupRecord f v)) | (f, t) <- fs ] + TVFun _ t -> VFun (\x -> loop t (fromVFun v x)) diff --git a/src/Cryptol/Symbolic/Value.hs b/src/Cryptol/Symbolic/Value.hs index 20e1b796..198cb2d5 100644 --- a/src/Cryptol/Symbolic/Value.hs +++ b/src/Cryptol/Symbolic/Value.hs @@ -9,137 +9,101 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Cryptol.Symbolic.Value where -import Control.Applicative -import Control.Monad.Fix (MonadFix) -import Control.Monad.IO.Class -import Control.Monad.Reader (MonadReader, ReaderT, runReaderT) -import Control.Monad.Trans (lift) import Data.Bits (bitSize) -import Data.IORef -import Data.Traversable import Cryptol.Eval.Value (TValue) import Cryptol.Symbolic.BitVector import Cryptol.TypeCheck.AST import Cryptol.Utils.Panic (panic) -import Data.SBV (Symbolic, SBool, SMTConfig, fromBitsBE, sbvTestBit) +import Data.SBV (SBool, fromBitsBE, sbvTestBit, Mergeable(..)) --- Symbolic Simulator Monad ---------------------------------------------------- - -data SimEnv = SimEnv - { simConfig :: SMTConfig - , simPath :: SBool - , simIteSolver :: Bool - , simVerbose :: Bool - } - -newtype TheMonad a = TheMonad (ReaderT SimEnv Symbolic a) - deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadReader SimEnv) - --- ^ The SBool environment parameter models the path condition, i.e. --- an assertion derived from the context of surrounding if-then-else --- statements. It is used to determine whether new if-then-else --- branches are reachable. It is important to include this within the --- monad so that a conditional in a function body will be evaluated --- relative to the path condition where the function is applied, --- rather than with the path condition where the function was defined. - -runTheMonad :: TheMonad a -> SimEnv -> Symbolic a -runTheMonad (TheMonad r) = runReaderT r - -liftSymbolic :: Symbolic a -> TheMonad a -liftSymbolic s = TheMonad (lift s) - --- Values and Thunks ----------------------------------------------------------- +-- Values ---------------------------------------------------------------------- data Value - = VRecord [(Name, Thunk)] -- @ { .. } @ - | VTuple [Thunk] -- @ ( .. ) @ + = VRecord [(Name, Value)] -- @ { .. } @ + | VTuple [Value] -- @ ( .. ) @ | VBit SBool -- @ Bit @ | VWord SWord -- @ [n]Bit @ | VNil - | VCons Thunk Thunk -- @ [n]a @ head, tail - | VFun (Thunk -> TheMonad Value) -- functions - | VPoly (TValue -> TheMonad Value) -- polymorphic values (kind *) + | VCons Value Value -- @ [n]a @ head, tail + | VFun (Value -> Value) -- functions + | VPoly (TValue -> Value) -- polymorphic values (kind *) -data Thunk - = Thunk (IORef (Either (TheMonad Value) Value)) - | Ready Value +-- Symbolic Conditionals ------------------------------------------------------- -force :: Thunk -> TheMonad Value -force (Ready v) = return v -force (Thunk ref) = do - r <- liftIO $ readIORef ref - case r of - Left m -> do - v <- m - liftIO $ writeIORef ref (Right v) - return v - Right v -> return v - -delay :: TheMonad Value -> TheMonad Thunk -delay m = Thunk <$> liftIO (newIORef (Left m)) - -ready :: Value -> TheMonad Thunk -ready v = Thunk <$> liftIO (newIORef (Right v)) +instance Mergeable Value where + symbolicMerge c v1 v2 = + case (v1, v2) of + (VRecord fs1, VRecord fs2) -> VRecord $ zipWith mergeField fs1 fs2 + (VTuple vs1 , VTuple vs2 ) -> VTuple $ zipWith (symbolicMerge c) vs1 vs2 + (VBit b1 , VBit b2 ) -> VBit $ symbolicMerge c b1 b2 + (VWord w1 , VWord w2 ) -> VWord $ symbolicMerge c w1 w2 + (VNil , VNil ) -> VNil + (VCons h1 t1, VCons h2 t2) -> VCons (symbolicMerge c h1 h2) (symbolicMerge c t1 t2) + (VFun f1 , VFun f2 ) -> VFun $ symbolicMerge c f1 f2 + (VPoly f1 , VPoly f2 ) -> VPoly $ symbolicMerge c f1 f2 + (VWord w1 , _ ) -> VWord $ symbolicMerge c w1 (fromWord v2) + (_ , VWord w2 ) -> VWord $ symbolicMerge c (fromWord v1) w2 + (_ , _ ) -> error "symbolicMerge: incompatible values" + where + mergeField (n1, x1) (n2, x2) + | n1 == n2 = (n1, symbolicMerge c x1 x2) + | otherwise = error "symbolicMerge: incompatible values" -- Constructors and Accessors -------------------------------------------------- tlam :: (TValue -> Value) -> Value -tlam f = VPoly (return . f) +tlam f = VPoly f -vSeq :: [Thunk] -> Value +vSeq :: [Value] -> Value vSeq [] = VNil -vSeq (x : xs) = VCons x (Ready (vSeq xs)) +vSeq (x : xs) = VCons x (vSeq xs) -vApply :: Value -> Value -> TheMonad Value -vApply (VFun f) v = f (Ready v) -vApply _ _ = fail "vApply: not a function" +vApply :: Value -> Value -> Value +vApply (VFun f) v = f v +vApply _ _ = error "vApply: not a function" fromVBit :: Value -> SBool fromVBit (VBit b) = b fromVBit _ = error "fromVBit: not a bit" -fromWord :: Value -> TheMonad SWord -fromWord (VWord s) = return s -fromWord v = do - thunks <- fromSeq v - vs <- traverse force thunks - let bs = map fromVBit vs - return $ Data.SBV.fromBitsBE bs +fromWord :: Value -> SWord +fromWord (VWord s) = s +fromWord v = Data.SBV.fromBitsBE (map fromVBit (fromSeq v)) -fromSeq :: Value -> TheMonad [Thunk] -fromSeq VNil = return [] -fromSeq (VCons x th) = do - v <- force th - xs <- fromSeq v - return (x : xs) -fromSeq (VWord s) = return $ reverse [ Ready (VBit (sbvTestBit s i)) | i <- [0 .. bitSize s - 1] ] +fromSeq :: Value -> [Value] +fromSeq VNil = [] +fromSeq (VCons x xs) = x : fromSeq xs +fromSeq (VWord s) = [ VBit (sbvTestBit s i) | i <- reverse [0 .. bitSize s - 1] ] fromSeq _ = error "fromSeq: not a sequence" -fromVCons :: Value -> (Thunk, Thunk) +fromVCons :: Value -> (Value, Value) fromVCons (VCons h t) = (h, t) -fromVCons (VWord s) = fromVCons (foldr (\h t -> VCons (Ready h) (Ready t)) VNil bs) +fromVCons (VWord s) = fromVCons (foldr (\h t -> VCons h t) VNil bs) where bs = reverse [ VBit (sbvTestBit s i) | i <- [0 .. bitSize s - 1] ] fromVCons _ = error "fromVCons: not a stream" -fromVFun :: Value -> (Thunk -> TheMonad Value) +fromVFun :: Value -> (Value -> Value) fromVFun (VFun f) = f fromVFun _ = error "fromVFun: not a function" -fromVTuple :: Value -> [Thunk] -fromVTuple (VTuple thunks) = thunks +fromVPoly :: Value -> (TValue -> Value) +fromVPoly (VPoly f) = f +fromVPoly _ = error "fromVPoly: not a function" + +fromVTuple :: Value -> [Value] +fromVTuple (VTuple vs) = vs fromVTuple _ = error "fromVTuple: not a tuple" -fromVRecord :: Value -> [(Name, Thunk)] +fromVRecord :: Value -> [(Name, Value)] fromVRecord v = case v of VRecord fs -> fs _ -> evalPanic "fromVRecord" ["not a record"] -lookupRecord :: Name -> Value -> Thunk +lookupRecord :: Name -> Value -> Value lookupRecord f rec = case lookup f (fromVRecord rec) of - Just th -> th + Just v -> v Nothing -> error "lookupRecord" -- Errors ---------------------------------------------------------------------- From 907e4b55a9441d066b9e96deabf0f49d9e3601ef Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 22 Jul 2014 15:16:00 -0700 Subject: [PATCH 24/45] Fix typo in comment --- src/Cryptol/Prims/Eval.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cryptol/Prims/Eval.hs b/src/Cryptol/Prims/Eval.hs index 0b009903..fd8cd3da 100644 --- a/src/Cryptol/Prims/Eval.hs +++ b/src/Cryptol/Prims/Eval.hs @@ -508,7 +508,7 @@ ecSplitV = (Inf , Nat e) -> toStream $ mkChunks (infChunksOf e) _ -> evalPanic "splitV" ["invalid type arguments to split"] --- | Split into infinately many chunks +-- | Split into infinitely many chunks infChunksOf :: Integer -> [a] -> [[a]] infChunksOf each xs = let (as,bs) = genericSplitAt each xs in as : infChunksOf each bs From 6a99eb99dd2d3d11930fbdd533f34110bbd2212b Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 22 Jul 2014 15:32:26 -0700 Subject: [PATCH 25/45] Modify symbolic Value type to match the interpreter's Value type This is a step towards unifying the two modules. --- src/Cryptol/Symbolic.hs | 36 ++-- src/Cryptol/Symbolic/Prims.hs | 309 +++++++++++++++++----------------- src/Cryptol/Symbolic/Value.hs | 54 ++++-- 3 files changed, 205 insertions(+), 194 deletions(-) diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index d2e473ba..676af2c4 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -168,7 +168,7 @@ forallFinType ty = FTBit -> VBit <$> SBV.forall_ FTSeq 0 FTBit -> return $ VWord (SBV.literal (bv 0 0)) FTSeq n FTBit -> VWord <$> (forallBV_ n) - FTSeq n t -> vSeq <$> replicateM n (forallFinType t) + FTSeq n t -> VSeq False <$> replicateM n (forallFinType t) FTTuple ts -> VTuple <$> mapM forallFinType ts FTRecord fs -> VRecord <$> mapM (traverseSnd forallFinType) fs @@ -178,7 +178,7 @@ existsFinType ty = FTBit -> VBit <$> SBV.exists_ FTSeq 0 FTBit -> return $ VWord (SBV.literal (bv 0 0)) FTSeq n FTBit -> VWord <$> existsBV_ n - FTSeq n t -> vSeq <$> replicateM n (existsFinType t) + FTSeq n t -> VSeq False <$> replicateM n (existsFinType t) FTTuple ts -> VTuple <$> mapM existsFinType ts FTRecord fs -> VRecord <$> mapM (traverseSnd existsFinType) fs @@ -228,7 +228,7 @@ evalExpr :: Env -> Expr -> Value evalExpr env expr = case expr of ECon econ -> evalECon econ - EList es _ty -> vSeq (map eval es) + EList es ty -> VSeq False (map eval es) ETuple es -> VTuple (map eval es) ERec fields -> VRecord [ (f, eval e) | (f, e) <- fields ] ESel e sel -> evalSel sel (eval e) @@ -258,32 +258,21 @@ evalSel sel v = case sel of TupleSel n _ -> case v of - VTuple xs -> xs !! (n - 1) -- 1-based indexing - VNil -> VNil - VCons {} -> liftList v - VFun f -> VFun (\x -> evalSel sel (f x)) + VTuple xs -> xs !! (n - 1) -- 1-based indexing + VSeq b xs -> VSeq b (map (evalSel sel) xs) + VStream xs -> VStream (map (evalSel sel) xs) + VFun f -> VFun (\x -> evalSel sel (f x)) RecordSel n _ -> case v of VRecord bs -> fromJust (lookup n bs) - VNil -> VNil - VCons {} -> liftList v - VFun f -> VFun $ \x -> evalSel sel (f x) + VSeq b xs -> VSeq b (map (evalSel sel) xs) + VStream xs -> VStream (map (evalSel sel) xs) + VFun f -> VFun (\x -> evalSel sel (f x)) ListSel n _ -> case v of - --VSeq xs -> force $ xs !! n -- 0-based indexing VWord s -> VBit (SBV.sbvTestBit s n) - _ -> let go :: Int -> Value -> Value - go 0 (VCons x _) = x - go i (VCons _ y) = go (i - 1) y - go _ _ = error "internal error" - in go n v - - where - liftList VNil = VNil - liftList (VCons x xs) = VCons (evalSel sel x) (liftList xs) - liftList _ = panic "Cryptol.Symbolic.evalSel" - ["Malformed list, while lifting selector"] + _ -> fromSeq v !! n -- 0-based indexing -- Declarations ---------------------------------------------------------------- @@ -303,9 +292,10 @@ evalDecl env d = (dName d, evalExpr env (dDefinition d)) -- List Comprehensions --------------------------------------------------------- +-- | Evaluate a comprehension. evalComp :: Env -> TValue -> Expr -> [[Match]] -> Value evalComp env seqty body ms - | Just (_len, _el) <- isTSeq seqty = vSeq [ evalExpr e body | e <- envs ] + | Just (len,el) <- isTSeq seqty = toSeq len el [ evalExpr e body | e <- envs ] | otherwise = evalPanic "Cryptol.Eval" [ "evalComp given a non sequence" , show seqty ] diff --git a/src/Cryptol/Symbolic/Prims.hs b/src/Cryptol/Symbolic/Prims.hs index be2f102e..e9356b9b 100644 --- a/src/Cryptol/Symbolic/Prims.hs +++ b/src/Cryptol/Symbolic/Prims.hs @@ -12,13 +12,14 @@ module Cryptol.Symbolic.Prims where import Control.Applicative import Data.Bits +import Data.List (genericDrop, genericReplicate, genericSplitAt, genericTake, transpose) -import Cryptol.Eval.Value (TValue, numTValue, finTValue, isTSeq, isTBit, isTFun, isTTuple, isTRec) +import Cryptol.Eval.Value (TValue, numTValue, finTValue, isTSeq, isTBit, isTFun, isTTuple, isTRec, toNumTValue, tvSeq) import Cryptol.Prims.Syntax (ECon(..)) import Cryptol.Symbolic.BitVector import Cryptol.Symbolic.Value import Cryptol.TypeCheck.AST (Name) -import Cryptol.TypeCheck.Solver.InfNat(Nat'(..)) +import Cryptol.TypeCheck.Solver.InfNat(Nat'(..), nMul) import qualified Data.SBV as SBV import Data.SBV (SBool) @@ -163,53 +164,29 @@ evalECon econ = tlam $ \_ -> VFun $ \v -> VTuple [takeV a v, dropV a v] - ECJoin -> -- {a,b,c} (fin b) => [a][b]c -> [a * b]c + ECJoin -> tlam $ \ parts -> + tlam $ \ each -> + tlam $ \ a -> lam (joinV parts each a) + + ECSplit -> ecSplitV + + ECReverse -> tlam $ \a -> - tlam $ \_ -> - tlam $ \c -> - VFun $ \v -> - if not (numTValue a == Inf && isTBit c) then go v else go (mapV (vSeq . fromSeq) v) - -- avoid concatenating infinitely many VWords - where - go :: Value -> Value - go xss = do - case xss of - VNil -> VNil - VCons xs xss' -> catV xs (go xss') - _ -> error "join" - - ECSplit -> -- {a,b,c} (fin b) => [a * b] c -> [a][b] c - tlam $ \(numTValue -> a) -> - tlam $ \(finTValue -> b) -> - tlam $ \_ -> - VFun $ \v -> - let go :: Nat' -> Value -> Value - go t xs = - case dec t of - Nothing -> VNil - Just t' -> VCons (takeV b xs) (go t' (dropV b xs)) - in go a v - - ECReverse -> -- {a,b} (fin a) => [a] b -> [a] b - tlam $ \_ -> - tlam $ \_ -> - VFun $ \v -> vSeq (reverse (fromSeq v)) - - ECTranspose -> -- {a,b,c} [a][b]c -> [b][a]c - tlam $ \_a -> tlam $ \b -> - tlam $ \_c -> - VFun $ \v -> transp (numTValue b) v - where - hd :: Value -> Value - hd = fst . fromVCons - tl :: Value -> Value - tl = snd . fromVCons - transp :: Nat' -> Value -> Value - transp t xss = - case dec t of - Nothing -> VNil - Just t' -> VCons (mapV hd xss) (transp t' (mapV tl xss)) + lam $ \(fromSeq -> xs) -> toSeq a b (reverse xs) + + ECTranspose -> + tlam $ \a -> + tlam $ \b -> + tlam $ \c -> + lam $ \((map fromSeq . fromSeq) -> xs) -> + case numTValue a of + Nat 0 -> + let v = toSeq a c [] + in case numTValue b of + Nat n -> toSeq b (tvSeq a c) $ genericReplicate n v + Inf -> VStream $ repeat v + _ -> toSeq b (tvSeq a c) $ map (toSeq a c) $ transpose xs ECAt -> -- {n,a,i} (fin i) => [n]a -> [i] -> a tlam $ \_ -> @@ -249,58 +226,20 @@ evalECon econ = let err = zeroV a -- default for out-of-bounds accesses in mapV (selectV (\i -> nthV err xs (n - 1 - i))) ys - ECFromThen -> -- {first, next, bits, len} (...) => [len][bits] - tlam $ \first -> - tlam $ \next -> - tlam $ \bits -> - VPoly $ \len -> - let w = fromInteger (finTValue bits) - delta = finTValue next - finTValue first - lit i = VWord (SBV.literal (bv w i)) - go :: Integer -> Integer -> Value - go 0 _ = VNil - go n i = VCons (lit i) (go (n - 1) (i + delta)) - in go (finTValue len) (finTValue first) + ECFromThen -> fromThenV + ECFromTo -> fromToV + ECFromThenTo -> fromThenToV - ECFromTo -> -- {first, last, bits} (...) => [1 + (last - first)][bits] - tlam $ \first -> - tlam $ \lst -> - VPoly $ \bits -> - let w = fromInteger (finTValue bits) - lit i = VWord (SBV.literal (bv w i)) - go :: Integer -> Integer -> Value - go 0 _ = VNil - go n i = VCons (lit i) (go (n - 1) (i + 1)) - in go (finTValue lst - finTValue first + 1) (finTValue first) - - ECFromThenTo -> -- {first, next, last, bits, len} (...) => [len][bits] - tlam $ \first -> - tlam $ \next -> - tlam $ \_lst -> - tlam $ \bits -> - VPoly $ \len -> - let w = fromInteger (finTValue bits) - delta = finTValue next - finTValue first - lit i = VWord (SBV.literal (bv w i)) - go :: Integer -> Integer -> Value - go 0 _ = VNil - go n i = VCons (lit i) (go (n - 1) (i + delta)) - in go (finTValue len) (finTValue first) - - ECInfFrom -> -- {a} (fin a) => [a] -> [inf][a] - tlam $ \(finTValue -> a) -> - VFun $ \(fromWord -> x0) -> - let delta = SBV.literal (bv (fromInteger a) 1) - from x = VCons (VWord x) (from (x + delta)) - in from x0 + ECInfFrom -> + tlam $ \(finTValue -> bits) -> + lam $ \(fromWord -> first) -> + toStream [ VWord (first + SBV.literal (bv (fromInteger bits) i)) | i <- [0 ..] ] ECInfFromThen -> -- {a} (fin a) => [a] -> [a] -> [inf][a] tlam $ \_ -> - VFun $ \(fromWord -> x1) -> - VFun $ \(fromWord -> x2) -> - let delta = x2 - x1 - from x = VCons (VWord x) (from (x + delta)) - in from x1 + lam $ \(fromWord -> first) -> + lam $ \(fromWord -> next) -> + toStream (map VWord (iterate (+ (next - first)) first)) -- {at,len} (fin len) => [len][8] -> at ECError -> @@ -319,7 +258,7 @@ evalECon econ = xs = map fromVBit (fromSeq v1) ys = map fromVBit (fromSeq v2) zs = take (fromInteger k) (mul xs ys [] ++ repeat SBV.false) - in vSeq (map VBit zs) + in VSeq True (map VBit zs) ECPDiv -> -- {a,b} (fin a, fin b) => [a] -> [b] -> [a] tlam $ \(finTValue -> i) -> @@ -329,7 +268,7 @@ evalECon econ = let xs = map fromVBit (fromSeq v1) ys = map fromVBit (fromSeq v2) zs = take (fromInteger i) (fst (Poly.mdp (reverse xs) (reverse ys)) ++ repeat SBV.false) - in vSeq (map VBit (reverse zs)) + in VSeq True (map VBit (reverse zs)) ECPMod -> -- {a,b} (fin a, fin b) => [a] -> [b+1] -> [b] tlam $ \_ -> @@ -339,14 +278,10 @@ evalECon econ = let xs = map fromVBit (fromSeq v1) ys = map fromVBit (fromSeq v2) zs = take (fromInteger j) (snd (Poly.mdp (reverse xs) (reverse ys)) ++ repeat SBV.false) - in vSeq (map VBit (reverse zs)) + in VSeq True (map VBit (reverse zs)) ECRandom -> error "ECRandom" -dec :: Nat' -> Maybe Nat' -dec (Nat 0) = Nothing -dec (Nat n) = Just (Nat (n - 1)) -dec Inf = Just Inf selectV :: (Integer -> Value) -> Value -> Value selectV f v = sel 0 bits @@ -360,51 +295,53 @@ selectV f v = sel 0 bits m2 = sel offset bs replicateV :: Integer -> Value -> Value -replicateV n x = go n - where go 0 = VNil - go i = VCons x (go (i - 1)) +replicateV n x = VSeq False (genericReplicate n x) + +nth :: a -> [a] -> Int -> a +nth def [] _ = def +nth def (x : xs) n + | n == 0 = x + | otherwise = nth def xs (n - 1) nthV :: Value -> Value -> Integer -> Value -nthV err xs n = - case xs of - VCons x xs' | n == 0 -> x - | otherwise -> nthV err xs' (n - 1) +nthV err v n = + case v of + VStream xs -> nth err xs (fromInteger n) + VSeq _ xs -> nth err xs (fromInteger n) VWord x -> let i = bitSize x - 1 - fromInteger n in if i < 0 then err else VBit (SBV.sbvTestBit x i) _ -> err mapV :: (Value -> Value) -> Value -> Value -mapV f v = do +mapV f v = case v of - VNil -> VNil - VCons x xs -> VCons (f x) (mapV f xs) + VSeq b xs -> VSeq b (map f xs) + VStream xs -> VStream (map f xs) _ -> error "mapV" catV :: Value -> Value -> Value -catV xs ys = do - case xs of - VCons x xs' -> VCons x (catV xs' ys) - VNil -> ys - VWord x -> VWord (cat x (fromWord ys)) - _ -> error "catV" +catV (VWord x) ys = VWord (cat x (fromWord ys)) +catV xs (VWord y) = VWord (cat (fromWord xs) y) +catV (VSeq b xs) (VSeq _ ys) = VSeq b (xs ++ ys) +catV (VSeq _ xs) (VStream ys) = VStream (xs ++ ys) +catV _ _ = error "catV" dropV :: Integer -> Value -> Value dropV 0 xs = xs dropV n xs = case xs of - VCons _ xs' -> dropV (n - 1) xs' - VNil -> VNil + VSeq b xs' -> VSeq b (genericDrop n xs') + VStream xs' -> VStream (genericDrop n xs') VWord w -> VWord $ extract (bitSize w - 1 - fromInteger n) 0 w _ -> error "dropV" takeV :: Integer -> Value -> Value -takeV 0 _ = VNil takeV n xs = case xs of VWord w -> VWord $ extract (bitSize w - 1) (bitSize w - fromInteger n) w - VCons x xs' -> VCons x (takeV (n - 1) xs') - VNil -> VNil + VSeq b xs' -> VSeq b (genericTake n xs') + VStream xs' -> VStream (genericTake n xs') _ -> error "takeV" -- | Make a numeric constant. @@ -468,13 +405,8 @@ arithBinary op = loop . toTypeVal case ty of TVBit -> evalPanic "arithBinop" ["Invalid arguments"] TVSeq _ TVBit -> VWord (op (fromWord l) (fromWord r)) - TVSeq 0 _ -> VNil - TVSeq n t -> VCons (loop t hl hr) (loop (TVSeq (n - 1) t) tl tr) - where (hl, tl) = fromVCons l - (hr, tr) = fromVCons r - TVStream t -> VCons (loop t hl hr) (loop ty tl tr) - where (hl, tl) = fromVCons l - (hr, tr) = fromVCons r + TVSeq _ t -> VSeq False (zipWith (loop t) (fromSeq l) (fromSeq r)) + TVStream t -> VStream (zipWith (loop t) (fromSeq l) (fromSeq r)) TVTuple ts -> VTuple (zipWith3 loop ts (fromVTuple l) (fromVTuple r)) TVRecord fs -> VRecord [ (f, loop t (lookupRecord f l) (lookupRecord f r)) | (f, t) <- fs ] TVFun _ t -> VFun (\x -> loop t (fromVFun l x) (fromVFun r x)) @@ -487,11 +419,8 @@ arithUnary op = loop . toTypeVal case ty of TVBit -> evalPanic "arithUnary" ["Invalid arguments"] TVSeq _ TVBit -> VWord (op (fromWord v)) - TVSeq 0 _ -> VNil - TVSeq n t -> VCons (loop t vh) (loop (TVSeq (n - 1) t) vt) - where (vh, vt) = fromVCons v - TVStream t -> VCons (loop t vh) (loop ty vt) - where (vh, vt) = fromVCons v + TVSeq _ t -> VSeq False (map (loop t) (fromSeq v)) + TVStream t -> VStream (map (loop t) (fromSeq v)) TVTuple ts -> VTuple (zipWith loop ts (fromVTuple v)) TVRecord fs -> VRecord [ (f, loop t (lookupRecord f v)) | (f, t) <- fs ] TVFun _ t -> VFun (\x -> loop t (fromVFun v x)) @@ -524,8 +453,8 @@ cmpValue fb fw = cmp (VTuple vs1 , VTuple vs2 ) -> cmpValues vs1 vs2 k (VBit b1 , VBit b2 ) -> fb b1 b2 k (VWord w1 , VWord w2 ) -> fw w1 w2 k - (VNil , VNil ) -> k - (VCons h1 t1, VCons h2 t2) -> cmpValues [h1,t1] [h2,t2] k + (VSeq _ vs1 , VSeq _ vs2 ) -> cmpValues vs1 vs2 k + (VStream {} , VStream {} ) -> error "Infinite streams are not comparable" (VFun {} , VFun {} ) -> error "Functions are not comparable" (VPoly {} , VPoly {} ) -> error "Polymorphic values are not comparable" (VWord w1 , _ ) -> fw w1 (fromWord v2) k @@ -563,8 +492,8 @@ errorV msg = go . toTypeVal go ty = case ty of TVBit -> VBit (error msg) - TVSeq n t -> vSeq (replicate n (go t)) - TVStream t -> let v = VCons (go t) v in v + TVSeq n t -> VSeq False (replicate n (go t)) + TVStream t -> VStream (repeat (go t)) TVTuple ts -> VTuple [ go t | t <- ts ] TVRecord fs -> VRecord [ (n, go t) | (n, t) <- fs ] TVFun _ t -> VFun (const (go t)) @@ -576,12 +505,42 @@ zeroV = go . toTypeVal case ty of TVBit -> VBit SBV.false TVSeq n TVBit -> VWord (SBV.literal (bv n 0)) - TVSeq n t -> vSeq (replicate n (go t)) - TVStream t -> let v = VCons (go t) v in v + TVSeq n t -> VSeq False (replicate n (go t)) + TVStream t -> VStream (repeat (go t)) TVTuple ts -> VTuple [ go t | t <- ts ] TVRecord fs -> VRecord [ (n, go t) | (n, t) <- fs ] TVFun _ t -> VFun (const (go t)) +-- | Join a sequence of sequences into a single sequence. +joinV :: TValue -> TValue -> TValue -> Value -> Value +joinV parts each a v = + let len = toNumTValue (numTValue parts `nMul` numTValue each) + in toSeq len a (concatMap fromSeq (fromSeq v)) + +-- | Split implementation. +ecSplitV :: Value +ecSplitV = + tlam $ \ parts -> + tlam $ \ each -> + tlam $ \ a -> + lam $ \ v -> + let mkChunks f = map (toFinSeq a) $ f $ fromSeq v + in case (numTValue parts, numTValue each) of + (Nat p, Nat e) -> VSeq False $ mkChunks (finChunksOf p e) + (Inf , Nat e) -> toStream $ mkChunks (infChunksOf e) + _ -> evalPanic "splitV" ["invalid type arguments to split"] + +-- | Split into infinitely many chunks +infChunksOf :: Integer -> [a] -> [[a]] +infChunksOf each xs = let (as,bs) = genericSplitAt each xs + in as : infChunksOf each bs + +-- | Split into finately many chunks +finChunksOf :: Integer -> Integer -> [a] -> [[a]] +finChunksOf 0 _ _ = [] +finChunksOf parts each xs = let (as,bs) = genericSplitAt each xs + in as : finChunksOf (parts - 1) each bs + -- | Merge two values given a binop. This is used for and, or and xor. logicBinary :: (SBool -> SBool -> SBool) -> (SWord -> SWord -> SWord) -> Binary logicBinary bop op = loop . toTypeVal @@ -590,13 +549,8 @@ logicBinary bop op = loop . toTypeVal case ty of TVBit -> VBit (bop (fromVBit l) (fromVBit r)) TVSeq _ TVBit -> VWord (op (fromWord l) (fromWord r)) - TVSeq 0 _ -> VNil - TVSeq n t -> VCons (loop t hl hr) (loop (TVSeq (n - 1) t) tl tr) - where (hl, tl) = fromVCons l - (hr, tr) = fromVCons r - TVStream t -> VCons (loop t hl hr) (loop ty tl tr) - where (hl, tl) = fromVCons l - (hr, tr) = fromVCons r + TVSeq _ t -> VSeq False (zipWith (loop t) (fromSeq l) (fromSeq r)) + TVStream t -> VStream (zipWith (loop t) (fromSeq l) (fromSeq r)) TVTuple ts -> VTuple (zipWith3 loop ts (fromVTuple l) (fromVTuple r)) TVRecord fs -> VRecord [ (f, loop t (lookupRecord f l) (lookupRecord f r)) | (f, t) <- fs ] TVFun _ t -> VFun (\x -> loop t (fromVFun l x) (fromVFun r x)) @@ -608,11 +562,60 @@ logicUnary bop op = loop . toTypeVal case ty of TVBit -> VBit (bop (fromVBit v)) TVSeq _ TVBit -> VWord (op (fromWord v)) - TVSeq 0 _ -> VNil - TVSeq n t -> VCons (loop t vh) (loop (TVSeq (n - 1) t) vt) - where (vh, vt) = fromVCons v - TVStream t -> VCons (loop t vh) (loop ty vt) - where (vh, vt) = fromVCons v + TVSeq _ t -> VSeq False (map (loop t) (fromSeq v)) + TVStream t -> VStream (map (loop t) (fromSeq v)) TVTuple ts -> VTuple (zipWith loop ts (fromVTuple v)) TVRecord fs -> VRecord [ (f, loop t (lookupRecord f v)) | (f, t) <- fs ] TVFun _ t -> VFun (\x -> loop t (fromVFun v x)) + +-- @[ 0, 1 .. ]@ +fromThenV :: Value +fromThenV = + tlamN $ \ first -> + tlamN $ \ next -> + tlamN $ \ bits -> + tlamN $ \ len -> + case (first, next, len, bits) of + (Nat first', Nat next', Nat len', Nat bits') -> + let nums = enumFromThen first' next' + lit i = VWord (SBV.literal (bv (fromInteger bits') i)) + in VSeq False (genericTake len' (map lit nums)) + _ -> evalPanic "fromThenV" ["invalid arguments"] + +-- @[ 0 .. 10 ]@ +fromToV :: Value +fromToV = + tlamN $ \ first -> + tlamN $ \ lst -> + tlamN $ \ bits -> + case (first, lst, bits) of + + (Nat first', Nat lst', Nat bits') -> + let nums = enumFromThenTo first' (first' + 1) lst' + len = 1 + (lst' - first') + lit i = VWord (SBV.literal (bv (fromInteger bits') i)) + in VSeq False (genericTake len (map lit nums)) + + _ -> evalPanic "fromThenV" ["invalid arguments"] + +-- @[ 0, 1 .. 10 ]@ +fromThenToV :: Value +fromThenToV = + tlamN $ \ first -> + tlamN $ \ next -> + tlamN $ \ lst -> + tlamN $ \ bits -> + tlamN $ \ len -> + case (first, next, lst, len, bits) of + + (Nat first', Nat next', Nat lst', Nat len', Nat bits') -> + let nums = enumFromThenTo first' next' lst' + lit i = VWord (SBV.literal (bv (fromInteger bits') i)) + in VSeq False (genericTake len' (map lit nums)) + + _ -> evalPanic "fromThenV" ["invalid arguments"] + +-- Miscellaneous --------------------------------------------------------------- + +tlamN :: (Nat' -> Value) -> Value +tlamN f = VPoly (\x -> f (numTValue x)) diff --git a/src/Cryptol/Symbolic/Value.hs b/src/Cryptol/Symbolic/Value.hs index 198cb2d5..69152701 100644 --- a/src/Cryptol/Symbolic/Value.hs +++ b/src/Cryptol/Symbolic/Value.hs @@ -10,10 +10,12 @@ module Cryptol.Symbolic.Value where import Data.Bits (bitSize) +import Data.List (genericTake) -import Cryptol.Eval.Value (TValue) +import Cryptol.Eval.Value (TValue, isTBit, numTValue) import Cryptol.Symbolic.BitVector import Cryptol.TypeCheck.AST +import Cryptol.TypeCheck.Solver.InfNat(Nat'(..)) import Cryptol.Utils.Panic (panic) import Data.SBV (SBool, fromBitsBE, sbvTestBit, Mergeable(..)) @@ -25,8 +27,10 @@ data Value | VTuple [Value] -- @ ( .. ) @ | VBit SBool -- @ Bit @ | VWord SWord -- @ [n]Bit @ - | VNil - | VCons Value Value -- @ [n]a @ head, tail + | VSeq Bool [Value] -- @ [n]a @ + -- The boolean parameter indicates whether or not + -- this is a sequence of bits. + | VStream [Value] -- @ [inf]a @ | VFun (Value -> Value) -- functions | VPoly (TValue -> Value) -- polymorphic values (kind *) @@ -39,8 +43,8 @@ instance Mergeable Value where (VTuple vs1 , VTuple vs2 ) -> VTuple $ zipWith (symbolicMerge c) vs1 vs2 (VBit b1 , VBit b2 ) -> VBit $ symbolicMerge c b1 b2 (VWord w1 , VWord w2 ) -> VWord $ symbolicMerge c w1 w2 - (VNil , VNil ) -> VNil - (VCons h1 t1, VCons h2 t2) -> VCons (symbolicMerge c h1 h2) (symbolicMerge c t1 t2) + (VSeq b1 vs1, VSeq _ vs2 ) -> VSeq b1 $ symbolicMerge c vs1 vs2 + (VStream vs1, VStream vs2) -> VStream $ symbolicMerge c vs1 vs2 (VFun f1 , VFun f2 ) -> VFun $ symbolicMerge c f1 f2 (VPoly f1 , VPoly f2 ) -> VPoly $ symbolicMerge c f1 f2 (VWord w1 , _ ) -> VWord $ symbolicMerge c w1 (fromWord v2) @@ -51,14 +55,32 @@ instance Mergeable Value where | n1 == n2 = (n1, symbolicMerge c x1 x2) | otherwise = error "symbolicMerge: incompatible values" +-- Big-endian Words ------------------------------------------------------------ + +unpackWord :: SWord -> [SBool] +unpackWord s = [ sbvTestBit s i | i <- reverse [0 .. bitSize s - 1] ] + -- Constructors and Accessors -------------------------------------------------- +lam :: (Value -> Value) -> Value +lam = VFun + tlam :: (TValue -> Value) -> Value tlam f = VPoly f -vSeq :: [Value] -> Value -vSeq [] = VNil -vSeq (x : xs) = VCons x (vSeq xs) +-- | Generate a stream. +toStream :: [Value] -> Value +toStream = VStream + +toFinSeq :: TValue -> [Value] -> Value +toFinSeq elty = VSeq (isTBit elty) + +-- | Construct either a finite sequence, or a stream. In the finite case, +-- record whether or not the elements were bits, to aid pretty-printing. +toSeq :: TValue -> TValue -> [Value] -> Value +toSeq len elty vals = case numTValue len of + Nat n -> toFinSeq elty (genericTake n vals) + Inf -> toStream vals vApply :: Value -> Value -> Value vApply (VFun f) v = f v @@ -72,17 +94,13 @@ fromWord :: Value -> SWord fromWord (VWord s) = s fromWord v = Data.SBV.fromBitsBE (map fromVBit (fromSeq v)) +-- | Extract a sequence. fromSeq :: Value -> [Value] -fromSeq VNil = [] -fromSeq (VCons x xs) = x : fromSeq xs -fromSeq (VWord s) = [ VBit (sbvTestBit s i) | i <- reverse [0 .. bitSize s - 1] ] -fromSeq _ = error "fromSeq: not a sequence" - -fromVCons :: Value -> (Value, Value) -fromVCons (VCons h t) = (h, t) -fromVCons (VWord s) = fromVCons (foldr (\h t -> VCons h t) VNil bs) - where bs = reverse [ VBit (sbvTestBit s i) | i <- [0 .. bitSize s - 1] ] -fromVCons _ = error "fromVCons: not a stream" +fromSeq v = case v of + VSeq _ vs -> vs + VWord s -> map VBit (unpackWord s) + VStream vs -> vs + _ -> evalPanic "fromSeq" ["not a sequence"] fromVFun :: Value -> (Value -> Value) fromVFun (VFun f) = f From c8abc76fd1daeb384acad25fa7fd63b937a8ad31 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 22 Jul 2014 15:43:29 -0700 Subject: [PATCH 26/45] Remove unused length tag from interpreter tuple values --- src/Cryptol/Eval.hs | 4 ++-- src/Cryptol/Eval/Value.hs | 10 +++++----- src/Cryptol/Prims/Eval.hs | 32 ++++++++++++++++---------------- src/Cryptol/Symbolic.hs | 2 +- src/Cryptol/Testing/Exhaust.hs | 2 +- src/Cryptol/Testing/Random.hs | 8 ++++---- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Cryptol/Eval.hs b/src/Cryptol/Eval.hs index 62688044..ce9c7e7f 100644 --- a/src/Cryptol/Eval.hs +++ b/src/Cryptol/Eval.hs @@ -44,7 +44,7 @@ evalExpr env expr = case expr of EList es ty -> evalList env es (evalType env ty) - ETuple es -> VTuple (length es) (map eval es) + ETuple es -> VTuple (map eval es) ERec fields -> VRecord [ (f,eval e) | (f,e) <- fields ] @@ -138,7 +138,7 @@ evalSel env e sel = case sel of tupleSel n v = case v of - VTuple _ vs -> vs !! (n - 1) + VTuple vs -> vs !! (n - 1) VSeq False vs -> VSeq False [ tupleSel n v1 | v1 <- vs ] VStream vs -> VStream [ tupleSel n v1 | v1 <- vs ] VFun f -> VFun (\x -> tupleSel n (f x)) diff --git a/src/Cryptol/Eval/Value.hs b/src/Cryptol/Eval/Value.hs index d5c46ea6..4d23f470 100644 --- a/src/Cryptol/Eval/Value.hs +++ b/src/Cryptol/Eval/Value.hs @@ -74,7 +74,7 @@ finTValue tval = data Value = VRecord [(Name,Value)] -- @ { .. } @ - | VTuple Int [Value] -- @ ( .. ) @ + | VTuple [Value] -- @ ( .. ) @ | VBit Bool -- @ Bit @ | VSeq Bool [Value] -- @ [n]a @ -- The boolean parameter indicates whether or not @@ -112,7 +112,7 @@ ppValue opts = loop VRecord fs -> braces (sep (punctuate comma (map ppField fs))) where ppField (f,r) = pp f <+> char '=' <+> loop r - VTuple _ vals -> parens (sep (punctuate comma (map loop vals))) + VTuple vals -> parens (sep (punctuate comma (map loop vals))) VBit b | b -> text "True" | otherwise -> text "False" VSeq isWord vals @@ -297,10 +297,10 @@ fromVFun val = case val of _ -> evalPanic "fromVFun" ["not a function"] -- | Extract a tuple from a value. -fromVTuple :: Value -> (Int,[Value]) +fromVTuple :: Value -> [Value] fromVTuple val = case val of - VTuple len vs -> (len,vs) - _ -> evalPanic "fromVTuple" ["not a tuple"] + VTuple vs -> vs + _ -> evalPanic "fromVTuple" ["not a tuple"] -- | Extract a record from a value. fromVRecord :: Value -> [(Name,Value)] diff --git a/src/Cryptol/Prims/Eval.hs b/src/Cryptol/Prims/Eval.hs index fd8cd3da..3b794168 100644 --- a/src/Cryptol/Prims/Eval.hs +++ b/src/Cryptol/Prims/Eval.hs @@ -318,9 +318,9 @@ arithBinary op = loop -- tuples | Just (_,tys) <- isTTuple ty = - let (len,ls) = fromVTuple l - (_,rs) = fromVTuple r - in VTuple len (zipWith3 loop tys ls rs) + let ls = fromVTuple l + rs = fromVTuple r + in VTuple (zipWith3 loop tys ls rs) -- records | Just fs <- isTRec ty = @@ -348,8 +348,8 @@ arithUnary op = loop -- tuples | Just (_,tys) <- isTTuple ty = - let (len,as) = fromVTuple x - in VTuple len (zipWith loop tys as) + let as = fromVTuple x + in VTuple (zipWith loop tys as) -- records | Just fs <- isTRec ty = @@ -388,7 +388,7 @@ lexCompare ty l r -- tuples | Just (_,etys) <- isTTuple ty = - zipLexCompare etys (snd (fromVTuple l)) (snd (fromVTuple r)) + zipLexCompare etys (fromVTuple l) (fromVTuple r) -- records | Just fields <- isTRec ty = @@ -456,8 +456,8 @@ zeroV ty lam (\ _ -> zeroV bty) -- tuples - | Just (len,tys) <- isTTuple ty = - VTuple len (map zeroV tys) + | Just (_,tys) <- isTTuple ty = + VTuple (map zeroV tys) -- records | Just fields <- isTRec ty = @@ -479,12 +479,12 @@ splitAtV front back a val = -- needs to be first, assuming that we're on a little-endian machine. Nat rightWidth | aBit -> let i = fromWord val - in VTuple 2 [ word leftWidth (i `shiftR` fromInteger rightWidth) - , word rightWidth i ] + in VTuple [ word leftWidth (i `shiftR` fromInteger rightWidth) + , word rightWidth i ] _ -> let (ls,rs) = splitAt (fromInteger leftWidth) (fromSeq val) - in VTuple 2 [VSeq aBit ls, toSeq back a rs] + in VTuple [VSeq aBit ls, toSeq back a rs] where @@ -543,9 +543,9 @@ logicBinary op = loop Inf -> toStream (zipWith (loop aty) (fromSeq l) (fromSeq r)) | Just (_,etys) <- isTTuple ty = - let (s,ls) = fromVTuple l - (_,rs) = fromVTuple r - in VTuple s (zipWith3 loop etys ls rs) + let ls = fromVTuple l + rs = fromVTuple r + in VTuple (zipWith3 loop etys ls rs) | Just (_,bty) <- isTFun ty = lam $ \ a -> loop bty (fromVFun l a) (fromVFun r a) @@ -571,8 +571,8 @@ logicUnary op = loop | Just (len,ety) <- isTSeq ty = toSeq len ety (map (loop ety) (fromSeq val)) | Just (_,etys) <- isTTuple ty = - let (s,as) = fromVTuple val - in VTuple s (zipWith loop etys as) + let as = fromVTuple val + in VTuple (zipWith loop etys as) | Just (_,bty) <- isTFun ty = lam $ \ a -> loop bty (fromVFun val a) diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 676af2c4..8af3536f 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -118,7 +118,7 @@ parseValue (FTSeq n FTBit) cws = (Eval.VSeq True vs, cws') where (vs, cws') = parseValues (replicate n FTBit) cws parseValue (FTSeq n t) cws = (Eval.VSeq False vs, cws') where (vs, cws') = parseValues (replicate n t) cws -parseValue (FTTuple ts) cws = (Eval.VTuple (length vs) vs, cws') +parseValue (FTTuple ts) cws = (Eval.VTuple vs, cws') where (vs, cws') = parseValues ts cws parseValue (FTRecord fs) cws = (Eval.VRecord (zip ns vs), cws') where (ns, ts) = unzip fs diff --git a/src/Cryptol/Testing/Exhaust.hs b/src/Cryptol/Testing/Exhaust.hs index e36b524c..c23e1a32 100644 --- a/src/Cryptol/Testing/Exhaust.hs +++ b/src/Cryptol/Testing/Exhaust.hs @@ -97,7 +97,7 @@ typeValues ty = (TCFun, _) -> [] -- We don't generate function values. - (TCTuple n, els) -> [ VTuple n xs | xs <- sequence (map typeValues els)] + (TCTuple _, els) -> [ VTuple xs | xs <- sequence (map typeValues els)] (TCNewtype _, _) -> [] TCon _ _ -> [] diff --git a/src/Cryptol/Testing/Random.hs b/src/Cryptol/Testing/Random.hs index 1dde52f3..13f24e85 100644 --- a/src/Cryptol/Testing/Random.hs +++ b/src/Cryptol/Testing/Random.hs @@ -138,12 +138,12 @@ randomSequence w mkElem sz g = -- | Generate a random tuple value. randomTuple :: RandomGen g => [Gen g] -> Gen g -randomTuple gens sz = go 0 [] gens +randomTuple gens sz = go [] gens where - go !n els [] g = (VTuple n (reverse els), g) - go !n els (mkElem : more) g = + go els [] g = (VTuple (reverse els), g) + go els (mkElem : more) g = let (v, g1) = mkElem sz g - in go (n+1) (v : els) more g1 + in go (v : els) more g1 -- | Generate a random record value. randomRecord :: RandomGen g => [(Name, Gen g)] -> Gen g From ca53f226e54e6bb4affb965d03c5babd628788cc Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 22 Jul 2014 16:26:33 -0700 Subject: [PATCH 27/45] Factor out separate type "BV" for words in evaluator's Value datatype Instead of constructor VWord Integer Integer, we use VWord BV, where data BV = BV !Integer !Integer. --- src/Cryptol/Eval/Value.hs | 36 ++++++++++++++++++---------------- src/Cryptol/Prims/Eval.hs | 22 ++++++++++----------- src/Cryptol/Symbolic.hs | 6 +++--- src/Cryptol/Testing/Exhaust.hs | 2 +- src/Cryptol/Testing/Random.hs | 4 ++-- 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/Cryptol/Eval/Value.hs b/src/Cryptol/Eval/Value.hs index 4d23f470..fd96e971 100644 --- a/src/Cryptol/Eval/Value.hs +++ b/src/Cryptol/Eval/Value.hs @@ -72,6 +72,9 @@ finTValue tval = -- Values ---------------------------------------------------------------------- +data BV = BV !Integer !Integer -- ^ width, value + -- The value may contain junk bits + data Value = VRecord [(Name,Value)] -- @ { .. } @ | VTuple [Value] -- @ ( .. ) @ @@ -79,8 +82,7 @@ data Value | VSeq Bool [Value] -- @ [n]a @ -- The boolean parameter indicates whether or not -- this is a sequence of bits. - | VWord Integer Integer -- @ [n]Bit @ width,value. - -- The value may contain junk bits + | VWord BV -- @ [n]Bit @ | VStream [Value] -- @ [inf]a @ | VFun (Value -> Value) -- functions | VPoly (TValue -> Value) -- polymorphic values (kind *) @@ -116,9 +118,9 @@ ppValue opts = loop VBit b | b -> text "True" | otherwise -> text "False" VSeq isWord vals - | isWord -> uncurry (ppWord opts) (fromVWord val) + | isWord -> ppWord opts (fromVWord val) | otherwise -> ppWordSeq vals - VWord w i -> ppWord opts w (mask w i) + VWord (BV w i) -> ppWord opts (BV w (mask w i)) VStream vals -> brackets $ fsep $ punctuate comma ( take (useInfLength opts) (map loop vals) @@ -147,8 +149,8 @@ data WithBase a = WithBase PPOpts a instance PP (WithBase Value) where ppPrec _ (WithBase opts v) = ppValue opts v -ppWord :: PPOpts -> Integer -> Integer -> Doc -ppWord opts width i +ppWord :: PPOpts -> BV -> Doc +ppWord opts (BV width i) | base > 36 = integer i -- not sure how to rule this out | asciiMode opts width = text (show (toEnum (fromInteger i) :: Char)) | otherwise = prefix <> text value @@ -184,8 +186,8 @@ mask w i = i .&. ((1 `shiftL` fromInteger w) - 1) -- NOTE this assumes that the sequence of bits is big-endian and finite, so the -- first element of the list will be the most significant bit. -packWord :: [Bool] -> (Integer,Integer) -packWord bits = (toInteger w,a) +packWord :: [Bool] -> BV +packWord bits = BV (toInteger w) a where w = length bits a = foldl set 0 (zip [w - 1, w - 2 .. 0] bits) @@ -194,8 +196,8 @@ packWord bits = (toInteger w,a) -- NOTE this produces a list of bits that represent a big-endian word, so the -- most significant bit is the first element of the list. -unpackWord :: Integer -> Integer -> [Bool] -unpackWord w a = [ testBit a n | n <- [w' - 1, w' - 2 .. 0] ] +unpackWord :: BV -> [Bool] +unpackWord (BV w a) = [ testBit a n | n <- [w' - 1, w' - 2 .. 0] ] where w' = fromInteger w @@ -204,7 +206,7 @@ unpackWord w a = [ testBit a n | n <- [w' - 1, w' - 2 .. 0] ] -- | Create a packed word of n bits. word :: Integer -> Integer -> Value -word n i = VWord n (mask n i) +word n i = VWord (BV n (mask n i)) lam :: (Value -> Value) -> Value lam = VFun @@ -222,7 +224,7 @@ toFinSeq elty = VSeq (isTBit elty) -- | This is strict! boolToWord :: [Bool] -> Value -boolToWord = uncurry VWord . packWord +boolToWord = VWord . packWord -- | Construct either a finite sequence, or a stream. In the finite case, -- record whether or not the elements were bits, to aid pretty-printing. @@ -261,7 +263,7 @@ fromVBit val = case val of fromSeq :: Value -> [Value] fromSeq val = case val of VSeq _ vs -> vs - VWord w a -> map VBit (unpackWord w a) + VWord bv -> map VBit (unpackWord bv) VStream vs -> vs _ -> evalPanic "fromSeq" ["not a sequence"] @@ -270,16 +272,16 @@ fromStr = map (toEnum . fromInteger . fromWord) . fromSeq -- | Extract a packed word. -- Note that this does not clean-up any junk bits in the word. -fromVWord :: Value -> (Integer,Integer) +fromVWord :: Value -> BV fromVWord val = case val of - VWord w a -> (w,a) -- this should always mask + VWord bv -> bv -- this should always mask VSeq isWord bs | isWord -> packWord (map fromVBit bs) _ -> evalPanic "fromVWord" ["not a word", show $ ppValue defaultPPOpts val] vWordLen :: Value -> Maybe Integer vWordLen val = case val of - VWord w _ -> Just w + VWord (BV w _) -> Just w VSeq isWord bs | isWord -> Just (toInteger (length bs)) _ -> Nothing @@ -288,7 +290,7 @@ vWordLen val = case val of fromWord :: Value -> Integer fromWord val = mask w a where - (w,a) = fromVWord val + BV w a = fromVWord val -- | Extract a function from a value. fromVFun :: Value -> (Value -> Value) diff --git a/src/Cryptol/Prims/Eval.hs b/src/Cryptol/Prims/Eval.hs index 3b794168..78cffb04 100644 --- a/src/Cryptol/Prims/Eval.hs +++ b/src/Cryptol/Prims/Eval.hs @@ -206,7 +206,7 @@ ecDemoteV :: Value ecDemoteV = tlam $ \valT -> tlam $ \bitT -> case (numTValue valT, numTValue bitT) of - (Nat v, Nat bs) -> VWord bs v + (Nat v, Nat bs) -> VWord (BV bs v) _ -> evalPanic "Cryptol.Eval.Prim.evalConst" ["Unexpected Inf in constant." , show valT @@ -306,7 +306,7 @@ arithBinary op = loop | Just (len,a) <- isTSeq ty = case numTValue len of -- words and finite sequences - Nat w | isTBit a -> VWord w (op w (fromWord l) (fromWord r)) + Nat w | isTBit a -> VWord (BV w (op w (fromWord l) (fromWord r))) | otherwise -> VSeq False (zipWith (loop a) (fromSeq l) (fromSeq r)) -- streams @@ -337,7 +337,7 @@ arithUnary op = loop | Just (len,a) <- isTSeq ty = case numTValue len of -- words and finite sequences - Nat w | isTBit a -> VWord w (op (fromWord x)) + Nat w | isTBit a -> VWord (BV w (op (fromWord x))) | otherwise -> VSeq False (map (loop a) (fromSeq x)) Inf -> toStream (map (loop a) (fromSeq x)) @@ -535,7 +535,7 @@ logicBinary op = loop case numTValue len of -- words or finite sequences - Nat w | isTBit aty -> VWord w (op (fromWord l) (fromWord r)) + Nat w | isTBit aty -> VWord (BV w (op (fromWord l) (fromWord r))) | otherwise -> VSeq False (zipWith (loop aty) (fromSeq l) (fromSeq r)) @@ -565,8 +565,8 @@ logicUnary op = loop | isTBit ty = VBit (op (fromVBit val)) | Just (_,b) <- isTSeq ty - , isTBit b = let (w,a) = fromVWord val - in VWord w (op a) + , isTBit b = let BV w a = fromVWord val + in VWord (BV w (op a)) | Just (len,ety) <- isTSeq ty = toSeq len ety (map (loop ety) (fromSeq val)) @@ -596,8 +596,8 @@ logicShift opW opS lam $ \ r -> if isTBit c then -- words - let (w,i) = fromVWord l - in VWord w (opW w i (fromInteger (fromWord r))) + let BV w i = fromVWord l + in VWord (BV w (opW w i (fromInteger (fromWord r)))) else toSeq a c (opS a c (fromSeq l) (fromInteger (fromWord r))) @@ -715,7 +715,7 @@ fromThenV = case (first, next, len, bits) of (Nat first', Nat next', Nat len', Nat bits') -> let nums = enumFromThen first' next' - in VSeq False (genericTake len' (map (VWord bits') nums)) + in VSeq False (genericTake len' (map (VWord . BV bits') nums)) _ -> evalPanic "fromThenV" ["invalid arguments"] -- @[ 0 .. 10 ]@ @@ -729,7 +729,7 @@ fromToV = (Nat first', Nat lst', Nat bits') -> let nums = enumFromThenTo first' (first' + 1) lst' len = 1 + (lst' - first') - in VSeq False (genericTake len (map (VWord bits') nums)) + in VSeq False (genericTake len (map (VWord . BV bits') nums)) _ -> evalPanic "fromThenV" ["invalid arguments"] @@ -745,7 +745,7 @@ fromThenToV = (Nat first', Nat next', Nat lst', Nat len', Nat bits') -> let nums = enumFromThenTo first' next' lst' - in VSeq False (genericTake len' (map (VWord bits') nums)) + in VSeq False (genericTake len' (map (VWord . BV bits') nums)) _ -> evalPanic "fromThenV" ["invalid arguments"] diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 8af3536f..5ed12b4e 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -110,9 +110,9 @@ parseValues (t : ts) cws = (v : vs, cws'') parseValue :: FinType -> [SBV.CW] -> (Eval.Value, [SBV.CW]) parseValue FTBit [] = error "parseValue" parseValue FTBit (cw : cws) = (Eval.VBit (SBV.fromCW cw), cws) -parseValue (FTSeq 0 FTBit) cws = (Eval.VWord 0 0, cws) +parseValue (FTSeq 0 FTBit) cws = (Eval.VWord (Eval.BV 0 0), cws) parseValue (FTSeq n FTBit) (cw : cws) - | SBV.isBounded cw = (Eval.VWord (toInteger n) (SBV.fromCW cw), cws) + | SBV.isBounded cw = (Eval.VWord (Eval.BV (toInteger n) (SBV.fromCW cw)), cws) | otherwise = error "parseValue" parseValue (FTSeq n FTBit) cws = (Eval.VSeq True vs, cws') where (vs, cws') = parseValues (replicate n FTBit) cws @@ -228,7 +228,7 @@ evalExpr :: Env -> Expr -> Value evalExpr env expr = case expr of ECon econ -> evalECon econ - EList es ty -> VSeq False (map eval es) + EList es ty -> VSeq (tIsBit ty) (map eval es) ETuple es -> VTuple (map eval es) ERec fields -> VRecord [ (f, eval e) | (f, e) <- fields ] ESel e sel -> evalSel sel (eval e) diff --git a/src/Cryptol/Testing/Exhaust.hs b/src/Cryptol/Testing/Exhaust.hs index c23e1a32..c7692904 100644 --- a/src/Cryptol/Testing/Exhaust.hs +++ b/src/Cryptol/Testing/Exhaust.hs @@ -88,7 +88,7 @@ typeValues ty = (TCSeq, ts1) -> case map tNoUser ts1 of [ TCon (TC (TCNum n)) _, TCon (TC TCBit) [] ] -> - [ VWord n x | x <- [ 0 .. 2^n - 1 ] ] + [ VWord (BV n x) | x <- [ 0 .. 2^n - 1 ] ] [ TCon (TC (TCNum n)) _, t ] -> [ VSeq False xs | xs <- sequence $ genericReplicate n diff --git a/src/Cryptol/Testing/Random.hs b/src/Cryptol/Testing/Random.hs index 13f24e85..7d5dcb7b 100644 --- a/src/Cryptol/Testing/Random.hs +++ b/src/Cryptol/Testing/Random.hs @@ -11,7 +11,7 @@ {-# LANGUAGE BangPatterns #-} module Cryptol.Testing.Random where -import Cryptol.Eval.Value (Value(..),ppValue,defaultPPOpts) +import Cryptol.Eval.Value (BV(..),Value(..),ppValue,defaultPPOpts) import Cryptol.Utils.Panic (panic) import Cryptol.TypeCheck.AST (Name,Type(..),TCon(..),TC(..),tNoUser) @@ -119,7 +119,7 @@ randomWord w sz g = val = foldl' mk 0 $ genericTake (div (bits + 63) 64) (x' : xs) finalVal = if sz > 1 && bits > 0 then setBit val (bits - 1) else val - in (VWord w finalVal, g3) + in (VWord (BV w finalVal), g3) -- | Generate a random infinite stream value. From 6bb2cd796d8704d0b58aa58b1a173dd36d7db0cd Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Tue, 22 Jul 2014 17:35:06 -0700 Subject: [PATCH 28/45] Unify Value datatypes for interpreter and symbolic simulator Some Value constructors and destructors are now polymorphic as well. --- src/Cryptol/Eval/Value.hs | 51 +++++++++++++--------- src/Cryptol/Prims/Eval.hs | 14 +++--- src/Cryptol/Symbolic.hs | 5 +-- src/Cryptol/Symbolic/Prims.hs | 32 +++----------- src/Cryptol/Symbolic/Value.hs | 82 +++++++---------------------------- src/Cryptol/Testing/Random.hs | 2 +- 6 files changed, 64 insertions(+), 122 deletions(-) diff --git a/src/Cryptol/Eval/Value.hs b/src/Cryptol/Eval/Value.hs index fd96e971..2478e126 100644 --- a/src/Cryptol/Eval/Value.hs +++ b/src/Cryptol/Eval/Value.hs @@ -75,18 +75,21 @@ finTValue tval = data BV = BV !Integer !Integer -- ^ width, value -- The value may contain junk bits -data Value - = VRecord [(Name,Value)] -- @ { .. } @ - | VTuple [Value] -- @ ( .. ) @ - | VBit Bool -- @ Bit @ - | VSeq Bool [Value] -- @ [n]a @ - -- The boolean parameter indicates whether or not - -- this is a sequence of bits. - | VWord BV -- @ [n]Bit @ - | VStream [Value] -- @ [inf]a @ - | VFun (Value -> Value) -- functions - | VPoly (TValue -> Value) -- polymorphic values (kind *) +-- | Generic value type, parameterized by bit and word types. +data GenValue b w + = VRecord [(Name, GenValue b w)] -- @ { .. } @ + | VTuple [GenValue b w] -- @ ( .. ) @ + | VBit b -- @ Bit @ + | VSeq Bool [GenValue b w] -- @ [n]a @ + -- The boolean parameter indicates whether or not + -- this is a sequence of bits. + | VWord w -- @ [n]Bit @ + | VStream [GenValue b w] -- @ [inf]a @ + | VFun (GenValue b w -> GenValue b w) -- functions + | VPoly (TValue -> GenValue b w) -- polymorphic values (kind *) + +type Value = GenValue Bool BV -- | An evaluated type. -- These types do not contain type variables, type synonyms, or type functions. @@ -208,18 +211,18 @@ unpackWord (BV w a) = [ testBit a n | n <- [w' - 1, w' - 2 .. 0] ] word :: Integer -> Integer -> Value word n i = VWord (BV n (mask n i)) -lam :: (Value -> Value) -> Value +lam :: (GenValue b w -> GenValue b w) -> GenValue b w lam = VFun -- | A type lambda that expects a @Type@. -tlam :: (TValue -> Value) -> Value +tlam :: (TValue -> GenValue b w) -> GenValue b w tlam = VPoly -- | Generate a stream. -toStream :: [Value] -> Value +toStream :: [GenValue b w] -> GenValue b w toStream = VStream -toFinSeq :: TValue -> [Value] -> Value +toFinSeq :: TValue -> [GenValue b w] -> GenValue b w toFinSeq elty = VSeq (isTBit elty) -- | This is strict! @@ -228,7 +231,7 @@ boolToWord = VWord . packWord -- | Construct either a finite sequence, or a stream. In the finite case, -- record whether or not the elements were bits, to aid pretty-printing. -toSeq :: TValue -> TValue -> [Value] -> Value +toSeq :: TValue -> TValue -> [GenValue b w] -> GenValue b w toSeq len elty vals = case numTValue len of Nat n -> toFinSeq elty (genericTake n vals) Inf -> toStream vals @@ -254,7 +257,7 @@ toPackedSeq len elty vals = case numTValue len of -- Value Destructors ----------------------------------------------------------- -- | Extract a bit value. -fromVBit :: Value -> Bool +fromVBit :: GenValue b w -> b fromVBit val = case val of VBit b -> b _ -> evalPanic "fromVBit" ["not a Bit"] @@ -293,25 +296,31 @@ fromWord val = mask w a BV w a = fromVWord val -- | Extract a function from a value. -fromVFun :: Value -> (Value -> Value) +fromVFun :: GenValue b w -> (GenValue b w -> GenValue b w) fromVFun val = case val of VFun f -> f _ -> evalPanic "fromVFun" ["not a function"] +-- | Extract a polymorphic function from a value. +fromVPoly :: GenValue b w -> (TValue -> GenValue b w) +fromVPoly val = case val of + VPoly f -> f + _ -> evalPanic "fromVPoly" ["not a polymorphic value"] + -- | Extract a tuple from a value. -fromVTuple :: Value -> [Value] +fromVTuple :: GenValue b w -> [GenValue b w] fromVTuple val = case val of VTuple vs -> vs _ -> evalPanic "fromVTuple" ["not a tuple"] -- | Extract a record from a value. -fromVRecord :: Value -> [(Name,Value)] +fromVRecord :: GenValue b w -> [(Name, GenValue b w)] fromVRecord val = case val of VRecord fs -> fs _ -> evalPanic "fromVRecord" ["not a record"] -- | Lookup a field in a record. -lookupRecord :: Name -> Value -> Value +lookupRecord :: Name -> GenValue b w -> GenValue b w lookupRecord f rec = case lookup f (fromVRecord rec) of Just val -> val Nothing -> evalPanic "lookupRecord" ["malformed record"] diff --git a/src/Cryptol/Prims/Eval.hs b/src/Cryptol/Prims/Eval.hs index 78cffb04..3aa2217f 100644 --- a/src/Cryptol/Prims/Eval.hs +++ b/src/Cryptol/Prims/Eval.hs @@ -276,16 +276,18 @@ doubleAndAdd base0 expMask modulus = go 1 base0 expMask -- Operation Lifting ----------------------------------------------------------- -type Binary = TValue -> Value -> Value -> Value +type GenBinary b w = TValue -> GenValue b w -> GenValue b w -> GenValue b w +type Binary = GenBinary Bool BV -binary :: Binary -> Value +binary :: GenBinary b w -> GenValue b w binary f = tlam $ \ ty -> lam $ \ a -> lam $ \ b -> f ty a b -type Unary = TValue -> Value -> Value +type GenUnary b w = TValue -> GenValue b w -> GenValue b w +type Unary = GenUnary Bool BV -unary :: Unary -> Value +unary :: GenUnary b w -> GenValue b w unary f = tlam $ \ ty -> lam $ \ a -> f ty a @@ -513,7 +515,7 @@ infChunksOf :: Integer -> [a] -> [[a]] infChunksOf each xs = let (as,bs) = genericSplitAt each xs in as : infChunksOf each bs --- | Split into finately many chunks +-- | Split into finitely many chunks finChunksOf :: Integer -> Integer -> [a] -> [[a]] finChunksOf 0 _ _ = [] finChunksOf parts each xs = let (as,bs) = genericSplitAt each xs @@ -763,5 +765,5 @@ randomV ty seed = -- Miscellaneous --------------------------------------------------------------- -tlamN :: (Nat' -> Value) -> Value +tlamN :: (Nat' -> GenValue b w) -> GenValue b w tlamN f = VPoly (\x -> f (numTValue x)) diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 5ed12b4e..63ab1ef7 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -32,7 +32,6 @@ import Cryptol.Symbolic.Prims import Cryptol.Symbolic.Value import qualified Cryptol.Eval.Value as Eval -import Cryptol.Eval.Value (TValue, isTSeq) import qualified Cryptol.Eval.Type (evalType) import qualified Cryptol.Eval.Env (EvalEnv(..)) import Cryptol.TypeCheck.AST @@ -58,7 +57,7 @@ prove (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolve let v = evalExpr env expr result <- SBV.proveWith prover $ do args <- mapM forallFinType ts - b <- return $! fromVBit (foldl vApply v args) + b <- return $! fromVBit (foldl fromVFun v args) when verbose $ liftIO $ putStrLn $ "Calling " ++ proverName ++ "..." return b let solution = case result of @@ -80,7 +79,7 @@ sat (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolverI let v = evalExpr env expr result <- SBV.satWith prover $ do args <- mapM existsFinType ts - b <- return $! fromVBit (foldl vApply v args) + b <- return $! fromVBit (foldl fromVFun v args) when verbose $ liftIO $ putStrLn $ "Calling " ++ proverName ++ "..." return b let solution = case result of diff --git a/src/Cryptol/Symbolic/Prims.hs b/src/Cryptol/Symbolic/Prims.hs index e9356b9b..9bddfdde 100644 --- a/src/Cryptol/Symbolic/Prims.hs +++ b/src/Cryptol/Symbolic/Prims.hs @@ -14,7 +14,7 @@ import Control.Applicative import Data.Bits import Data.List (genericDrop, genericReplicate, genericSplitAt, genericTake, transpose) -import Cryptol.Eval.Value (TValue, numTValue, finTValue, isTSeq, isTBit, isTFun, isTTuple, isTRec, toNumTValue, tvSeq) +import Cryptol.Prims.Eval (binary, unary, tlamN) import Cryptol.Prims.Syntax (ECon(..)) import Cryptol.Symbolic.BitVector import Cryptol.Symbolic.Value @@ -61,7 +61,7 @@ evalECon econ = tlam $ \b -> VFun $ \f -> VFun $ \g -> - VFun $ \x -> cmpBinary cmpEq cmpEq SBV.true b (vApply f x) (vApply g x) + VFun $ \x -> cmpBinary cmpEq cmpEq SBV.true b (fromVFun f x) (fromVFun g x) ECFunNotEq -> -- {a b} (Cmp b) => (a -> b) -> (a -> b) -> a -> Bit -- (f !== g) x = (f x != g x) @@ -69,7 +69,7 @@ evalECon econ = tlam $ \b -> VFun $ \f -> VFun $ \g -> - VFun $ \x -> cmpBinary cmpNotEq cmpNotEq SBV.false b (vApply f x) (vApply g x) + VFun $ \x -> cmpBinary cmpNotEq cmpNotEq SBV.false b (fromVFun f x) (fromVFun g x) ECMin -> -- {a} (Cmp a) => a -> a -> a -- min x y = if x <= y then x else y @@ -357,22 +357,6 @@ ecDemoteV = tlam $ \valT -> , show bitT ] - --- Operation Lifting ----------------------------------------------------------- - -type Binary = TValue -> Value -> Value -> Value - -binary :: Binary -> Value -binary f = VPoly $ \ty -> - VFun $ \v1 -> - VFun $ \v2 -> f ty v1 v2 - -type Unary = TValue -> Value -> Value - -unary :: Unary -> Value -unary f = VPoly $ \ty -> - VFun $ \v -> f ty v - -- Type Values ----------------------------------------------------------------- -- | An easy-to-use alternative representation for type `TValue`. @@ -397,6 +381,9 @@ toTypeVal ty -- Arith ----------------------------------------------------------------------- +type Binary = TValue -> Value -> Value -> Value +type Unary = TValue -> Value -> Value + -- | Models functions of type `{a} (Arith a) => a -> a -> a` arithBinary :: (SWord -> SWord -> SWord) -> Binary arithBinary op = loop . toTypeVal @@ -535,7 +522,7 @@ infChunksOf :: Integer -> [a] -> [[a]] infChunksOf each xs = let (as,bs) = genericSplitAt each xs in as : infChunksOf each bs --- | Split into finately many chunks +-- | Split into finitely many chunks finChunksOf :: Integer -> Integer -> [a] -> [[a]] finChunksOf 0 _ _ = [] finChunksOf parts each xs = let (as,bs) = genericSplitAt each xs @@ -614,8 +601,3 @@ fromThenToV = in VSeq False (genericTake len' (map lit nums)) _ -> evalPanic "fromThenV" ["invalid arguments"] - --- Miscellaneous --------------------------------------------------------------- - -tlamN :: (Nat' -> Value) -> Value -tlamN f = VPoly (\x -> f (numTValue x)) diff --git a/src/Cryptol/Symbolic/Value.hs b/src/Cryptol/Symbolic/Value.hs index 69152701..94d28d7e 100644 --- a/src/Cryptol/Symbolic/Value.hs +++ b/src/Cryptol/Symbolic/Value.hs @@ -6,33 +6,33 @@ -- Stability : provisional -- Portability : portable +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -module Cryptol.Symbolic.Value where +{-# LANGUAGE TypeSynonymInstances #-} + +module Cryptol.Symbolic.Value + ( Value + , TValue, numTValue, toNumTValue, finTValue, isTBit, isTFun, isTSeq, isTTuple, isTRec, tvSeq + , GenValue(..), lam, tlam, toStream, toFinSeq, toSeq + , fromVBit, fromVFun, fromVPoly, fromVTuple, fromVRecord, lookupRecord + , fromSeq, fromWord + , evalPanic + ) + where import Data.Bits (bitSize) -import Data.List (genericTake) -import Cryptol.Eval.Value (TValue, isTBit, numTValue) +import Cryptol.Eval.Value (TValue, numTValue, toNumTValue, finTValue, isTBit, isTFun, isTSeq, isTTuple, isTRec, tvSeq, + GenValue(..), lam, tlam, toStream, toFinSeq, toSeq, + fromVBit, fromVFun, fromVPoly, fromVTuple, fromVRecord, lookupRecord) import Cryptol.Symbolic.BitVector -import Cryptol.TypeCheck.AST -import Cryptol.TypeCheck.Solver.InfNat(Nat'(..)) import Cryptol.Utils.Panic (panic) import Data.SBV (SBool, fromBitsBE, sbvTestBit, Mergeable(..)) -- Values ---------------------------------------------------------------------- -data Value - = VRecord [(Name, Value)] -- @ { .. } @ - | VTuple [Value] -- @ ( .. ) @ - | VBit SBool -- @ Bit @ - | VWord SWord -- @ [n]Bit @ - | VSeq Bool [Value] -- @ [n]a @ - -- The boolean parameter indicates whether or not - -- this is a sequence of bits. - | VStream [Value] -- @ [inf]a @ - | VFun (Value -> Value) -- functions - | VPoly (TValue -> Value) -- polymorphic values (kind *) +type Value = GenValue SBool SWord -- Symbolic Conditionals ------------------------------------------------------- @@ -62,34 +62,6 @@ unpackWord s = [ sbvTestBit s i | i <- reverse [0 .. bitSize s - 1] ] -- Constructors and Accessors -------------------------------------------------- -lam :: (Value -> Value) -> Value -lam = VFun - -tlam :: (TValue -> Value) -> Value -tlam f = VPoly f - --- | Generate a stream. -toStream :: [Value] -> Value -toStream = VStream - -toFinSeq :: TValue -> [Value] -> Value -toFinSeq elty = VSeq (isTBit elty) - --- | Construct either a finite sequence, or a stream. In the finite case, --- record whether or not the elements were bits, to aid pretty-printing. -toSeq :: TValue -> TValue -> [Value] -> Value -toSeq len elty vals = case numTValue len of - Nat n -> toFinSeq elty (genericTake n vals) - Inf -> toStream vals - -vApply :: Value -> Value -> Value -vApply (VFun f) v = f v -vApply _ _ = error "vApply: not a function" - -fromVBit :: Value -> SBool -fromVBit (VBit b) = b -fromVBit _ = error "fromVBit: not a bit" - fromWord :: Value -> SWord fromWord (VWord s) = s fromWord v = Data.SBV.fromBitsBE (map fromVBit (fromSeq v)) @@ -102,28 +74,6 @@ fromSeq v = case v of VStream vs -> vs _ -> evalPanic "fromSeq" ["not a sequence"] -fromVFun :: Value -> (Value -> Value) -fromVFun (VFun f) = f -fromVFun _ = error "fromVFun: not a function" - -fromVPoly :: Value -> (TValue -> Value) -fromVPoly (VPoly f) = f -fromVPoly _ = error "fromVPoly: not a function" - -fromVTuple :: Value -> [Value] -fromVTuple (VTuple vs) = vs -fromVTuple _ = error "fromVTuple: not a tuple" - -fromVRecord :: Value -> [(Name, Value)] -fromVRecord v = case v of - VRecord fs -> fs - _ -> evalPanic "fromVRecord" ["not a record"] - -lookupRecord :: Name -> Value -> Value -lookupRecord f rec = case lookup f (fromVRecord rec) of - Just v -> v - Nothing -> error "lookupRecord" - -- Errors ---------------------------------------------------------------------- evalPanic :: String -> [String] -> a diff --git a/src/Cryptol/Testing/Random.hs b/src/Cryptol/Testing/Random.hs index 7d5dcb7b..32ea5056 100644 --- a/src/Cryptol/Testing/Random.hs +++ b/src/Cryptol/Testing/Random.hs @@ -11,7 +11,7 @@ {-# LANGUAGE BangPatterns #-} module Cryptol.Testing.Random where -import Cryptol.Eval.Value (BV(..),Value(..),ppValue,defaultPPOpts) +import Cryptol.Eval.Value (BV(..),Value,GenValue(..),ppValue,defaultPPOpts) import Cryptol.Utils.Panic (panic) import Cryptol.TypeCheck.AST (Name,Type(..),TCon(..),TC(..),tNoUser) From 7e876be060f4f2a40f2d430fc19320914156242b Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Wed, 23 Jul 2014 10:10:54 -0700 Subject: [PATCH 29/45] Remove additions to local fork of SBV that are no longer necessary --- sbv/Data/SBV.hs | 2 -- sbv/Data/SBV/Provers/Prover.hs | 44 ---------------------------------- sbv/Data/SBV/SMT/SMT.hs | 2 +- 3 files changed, 1 insertion(+), 47 deletions(-) diff --git a/sbv/Data/SBV.hs b/sbv/Data/SBV.hs index 5b71291c..b7c6cb92 100644 --- a/sbv/Data/SBV.hs +++ b/sbv/Data/SBV.hs @@ -183,10 +183,8 @@ module Data.SBV ( , Predicate, Provable(..), Equality(..) -- ** Proving properties , prove, proveWith, isTheorem, isTheoremWith - , internalProveWith, internalIsTheoremWith, internalIsTheorem -- ** Checking satisfiability , sat, satWith, isSatisfiable, isSatisfiableWith - , internalSatWith, internalIsSatisfiable, internalIsSatisfiableWith -- ** Finding all satisfying assignments , allSat, allSatWith -- ** Satisfying a sequence of boolean conditions diff --git a/sbv/Data/SBV/Provers/Prover.hs b/sbv/Data/SBV/Provers/Prover.hs index 8df9ab31..f3c6f501 100644 --- a/sbv/Data/SBV/Provers/Prover.hs +++ b/sbv/Data/SBV/Provers/Prover.hs @@ -27,8 +27,6 @@ module Data.SBV.Provers.Prover ( , boolector, cvc4, yices, z3, mathSAT, defaultSMTCfg , compileToSMTLib, generateSMTBenchmarks , isSBranchFeasibleInState - , internalSatWith, internalIsSatisfiableWith, internalIsSatisfiable - , internalProveWith, internalIsTheoremWith, internalIsTheorem ) where import Control.Monad (when, unless) @@ -282,30 +280,6 @@ isTheorem = isTheoremWith defaultSMTCfg isSatisfiable :: Provable a => Maybe Int -> a -> IO (Maybe Bool) isSatisfiable = isSatisfiableWith defaultSMTCfg -internalIsTheoremWith :: SMTConfig -> Maybe Int -> SBool -> Symbolic (Maybe Bool) -internalIsTheoremWith cfg mbTo p = do - r <- internalProveWith cfg{timeOut = mbTo} p - case r of - ThmResult (Unsatisfiable _) -> return $ Just True - ThmResult (Satisfiable _ _) -> return $ Just False - ThmResult (TimeOut _) -> return Nothing - _ -> error $ "SBV.isTheorem: Received:\n" ++ show r - -internalIsTheorem :: Maybe Int -> SBool -> Symbolic (Maybe Bool) -internalIsTheorem = internalIsTheoremWith defaultSMTCfg - -internalIsSatisfiableWith :: SMTConfig -> Maybe Int -> SBool -> Symbolic (Maybe Bool) -internalIsSatisfiableWith cfg mbTo p = do - r <- internalSatWith cfg{timeOut = mbTo} p - case r of - SatResult (Satisfiable _ _) -> return $ Just True - SatResult (Unsatisfiable _) -> return $ Just False - SatResult (TimeOut _) -> return Nothing - _ -> error $ "SBV.isSatisfiable: Received: " ++ show r - -internalIsSatisfiable :: Maybe Int -> SBool -> Symbolic (Maybe Bool) -internalIsSatisfiable = internalIsSatisfiableWith defaultSMTCfg - -- | Compiles to SMT-Lib and returns the resulting program as a string. Useful for saving -- the result to a file for off-line analysis, for instance if you have an SMT solver that's not natively -- supported out-of-the box by the SBV library. It takes two booleans: @@ -352,24 +326,6 @@ satWith :: Provable a => SMTConfig -> a -> IO SatResult satWith config a = simulate cvt config True [] a >>= callSolver True "Checking Satisfiability.." SatResult config where cvt = if useSMTLib2 config then toSMTLib2 else toSMTLib1 -internalProveWith :: SMTConfig -> SBool -> Symbolic ThmResult -internalProveWith config b = do - sw <- sbvToSymSW b - Result ki tr uic is cs ts as uis ax asgn cstr _ <- getResult - let res = Result ki tr uic is cs ts as uis ax asgn cstr [sw] - let cvt = if useSMTLib2 config then toSMTLib2 else toSMTLib1 - problem <- liftIO $ runProofOn cvt config False [] res - liftIO $ callSolver True "Checking Satisfiability.." ThmResult config problem - -internalSatWith :: SMTConfig -> SBool -> Symbolic SatResult -internalSatWith config b = do - sw <- sbvToSymSW b - Result ki tr uic is cs ts as uis ax asgn cstr _ <- getResult - let res = Result ki tr uic is cs ts as uis ax asgn cstr [sw] - let cvt = if useSMTLib2 config then toSMTLib2 else toSMTLib1 - problem <- liftIO $ runProofOn cvt config True [] res - liftIO $ callSolver True "Checking Satisfiability.." SatResult config problem - -- | Determine if the constraints are vacuous using the given SMT-solver isVacuousWith :: Provable a => SMTConfig -> a -> IO Bool isVacuousWith config a = do diff --git a/sbv/Data/SBV/SMT/SMT.hs b/sbv/Data/SBV/SMT/SMT.hs index 3df244c3..fbed6bbc 100644 --- a/sbv/Data/SBV/SMT/SMT.hs +++ b/sbv/Data/SBV/SMT/SMT.hs @@ -64,7 +64,7 @@ instance Show SatResult where "Unknown" "Unknown. Potential model:\n" "Satisfiable" "Satisfiable. Model:\n" r --- | The Show instance of AllSatResults. Note that we have to be careful in being lazy enough +-- | The Show instance of AllSatResults. Note that we have to be careful in being lazy enough -- as the typical use case is to pull results out as they become available. instance Show AllSatResult where show (AllSatResult (e, xs)) = go (0::Int) xs From c8a80b4186f1aafe3c51ea199de9eb7846a37955 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Wed, 23 Jul 2014 10:25:59 -0700 Subject: [PATCH 30/45] Remove more no-longer-necessary local additions to SBV --- sbv/Data/SBV/BitVectors/Data.hs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sbv/Data/SBV/BitVectors/Data.hs b/sbv/Data/SBV/BitVectors/Data.hs index a225a5a8..810c7bdc 100644 --- a/sbv/Data/SBV/BitVectors/Data.hs +++ b/sbv/Data/SBV/BitVectors/Data.hs @@ -40,14 +40,13 @@ module Data.SBV.BitVectors.Data , Quantifier(..), needsExistentials , SMTLibPgm(..), SMTLibVersion(..) , SolverCapabilities(..) - , extractSymbolicSimulationState, getResult + , extractSymbolicSimulationState , SMTScript(..), Solver(..), SMTSolver(..), SMTResult(..), SMTModel(..), SMTConfig(..), getSBranchRunConfig ) where import Control.DeepSeq (NFData(..)) import Control.Applicative (Applicative) import Control.Monad (when) -import Control.Monad.Fix (MonadFix) import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT) import Control.Monad.Trans (MonadIO, liftIO) import Data.Char (isAlpha, isAlphaNum) @@ -806,7 +805,7 @@ sbvToSW st (SBV _ (Right f)) = uncache f st -- state of the computation, layered on top of IO for creating unique -- references to hold onto intermediate results. newtype Symbolic a = Symbolic (ReaderT State IO a) - deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadReader State) + deriving (Applicative, Functor, Monad, MonadIO, MonadReader State) -- | Create a symbolic value, based on the quantifier we have. If an explicit quantifier is given, we just use that. -- If not, then we pick existential for SAT calls and universal for everything else. @@ -969,11 +968,6 @@ extractSymbolicSimulationState st@State{ spgm=pgm, rinps=inps, routs=outs, rtblM extraCstrs <- reverse `fmap` readIORef cstrs return $ Result knds traceVals cgMap inpsO cnsts tbls arrs unint axs (SBVPgm rpgm) extraCstrs outsO -getResult :: Symbolic Result -getResult = do - st <- ask - liftIO $ extractSymbolicSimulationState st - ------------------------------------------------------------------------------- -- * Symbolic Words ------------------------------------------------------------------------------- From aa935996b9fed209c9d3d64a0b03ad67043e8540 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Mon, 28 Jul 2014 12:08:15 -0700 Subject: [PATCH 31/45] Reimplement "logicUnary" so that complement works on infinite streams of bits --- src/Cryptol/Prims/Eval.hs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Cryptol/Prims/Eval.hs b/src/Cryptol/Prims/Eval.hs index 3aa2217f..2bc68b57 100644 --- a/src/Cryptol/Prims/Eval.hs +++ b/src/Cryptol/Prims/Eval.hs @@ -566,11 +566,16 @@ logicUnary op = loop loop ty val | isTBit ty = VBit (op (fromVBit val)) - | Just (_,b) <- isTSeq ty - , isTBit b = let BV w a = fromVWord val - in VWord (BV w (op a)) + | Just (len,ety) <- isTSeq ty = - | Just (len,ety) <- isTSeq ty = toSeq len ety (map (loop ety) (fromSeq val)) + case numTValue len of + + -- words or finite sequences + Nat w | isTBit ety -> VWord (BV w (op (fromWord val))) + | otherwise -> VSeq False (map (loop ety) (fromSeq val)) + + -- streams + Inf -> toStream (map (loop ety) (fromSeq val)) | Just (_,etys) <- isTTuple ty = let as = fromVTuple val From 5faf927b3e2e943c42501078778a91f20bf16b42 Mon Sep 17 00:00:00 2001 From: Dylan McNamee Date: Tue, 29 Jul 2014 09:32:14 -0700 Subject: [PATCH 32/45] Initial (incomplete, but mostly working) version of ChaChaPoly IETF draft. Original document: https://datatracker.ietf.org/doc/draft-irtf-cfrg-chacha20-poly1305/ --- examples/ChaChaPolyCryptolIETF.md | 1576 +++++++++++++++++++++++++++++ 1 file changed, 1576 insertions(+) create mode 100644 examples/ChaChaPolyCryptolIETF.md diff --git a/examples/ChaChaPolyCryptolIETF.md b/examples/ChaChaPolyCryptolIETF.md new file mode 100644 index 00000000..e16d8e3b --- /dev/null +++ b/examples/ChaChaPolyCryptolIETF.md @@ -0,0 +1,1576 @@ +% ChaCha20 and Poly1305 for IETF protocols +% Y. Nir (Check Point), A. Langley (Google Inc), D. McNamee (Galois, Inc) +% July 28, 2014 + +## Abstract + +This document defines the ChaCha20 stream cipher, as well as the use +of the Poly1305 authenticator, both as stand-alone algorithms, and as +a "combined mode", or Authenticated Encryption with Additional Data +(AEAD) algorithm. + +This document does not introduce any new crypto, but is meant to +serve as a stable reference and an implementation guide. + +This version of the document is a translation of the IETF draft document +draft-irtf-cfrg-chacha20-poly1305 into "literate Cryptol". +This document can be loaded and executed by a Cryptol interpreter. +There is an open source implementation of Cryptol available at http://cryptol.net + +## Copyright Notice + +Copyright (c) 2014 IETF Trust and the persons identified as the +document authors. All rights reserved. + +This document is subject to BCP 78 and the IETF Trust's Legal +Provisions Relating to IETF Documents +(http://trustee.ietf.org/license-info) in effect on the date of +publication of this document. Please review these documents +carefully, as they describe your rights and restrictions with respect +to this document. + +# Introduction + +The Advanced Encryption Standard (AES - [FIPS-197]) has become the +gold standard in encryption. Its efficient design, wide +implementation, and hardware support allow for high performance in +many areas. On most modern platforms, AES is anywhere from 4x to 10x +as fast as the previous most-used cipher, 3-key Data Encryption +Standard (3DES - [FIPS-46]), which makes it not only the best choice, +but the only practical choice. + +The problem is that if future advances in cryptanalysis reveal a +weakness in AES, users will be in an unenviable position. With the +only other widely supported cipher being the much slower 3DES, it is +not feasible to re-configure implementations to use 3DES. +[standby-cipher] describes this issue and the need for a standby +cipher in greater detail. + +This document defines such a standby cipher. We use ChaCha20 +([chacha]) with or without the Poly1305 ([poly1305]) authenticator. +These algorithms are not just fast. They are fast even in software- +only C-language implementations, allowing for much quicker deployment +when compared with algorithms such as AES that are significantly +accelerated by hardware implementations. + +This document does not introduce these new algorithms. They have +been defined in scientific papers by D. J. Bernstein, which are +referenced by this document. The purpose of this document is to +serve as a stable reference for IETF documents making use of these +algorithms. + +These algorithms have undergone rigorous analysis. Several papers +discuss the security of Salsa and ChaCha ([LatinDances], +[Zhenqing2012]). + +## Conventions Used in This Document + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in [RFC2119]. + +The description of the ChaCha algorithm will at various time refer to +the ChaCha state as a "vector" or as a "matrix". This follows the +use of these terms in DJB's paper. The matrix notation is more +visually convenient, and gives a better notion as to why some rounds +are called "column rounds" while others are called "diagonal rounds". +Here's a diagram of how matrices relate to vectors (using the C +language convention of zero being the index origin). + +```example + 0 , 1 , 2 , 3, + 4 , 5 , 6 , 7, + 8 , 9 , 10, 11, + 12, 13, 14, 15 +``` + +The elements in this vector or matrix are 32-bit unsigned integers. + +```cryptol +module ChaCha20 where + +type ChaChaState = [16][32] +``` + +The algorithm name is "ChaCha". "ChaCha20" is a specific instance where +20 "rounds" (or 80 quarter rounds - see "The ChaCha Quarter Round", below) +are used. Other variations are defined, with 8 or 12 rounds, but in this +document we only describe the 20-round ChaCha, so the names "ChaCha" +and "ChaCha20" will be used interchangeably. + +# The Algorithms + +The subsections below describe the algorithms used and the AEAD +construction. + +## The ChaCha Quarter Round + +The basic operation of the ChaCha algorithm is the quarter round. It +operates on four 32-bit unsigned integers, denoted a, b, c, and d. +The operation is as follows: + +```cryptol +ChaChaQuarterround : [4][32] -> [4][32] +ChaChaQuarterround [a, b, c, d] = [a'', b'', c'', d''] where + a' = a + b + d' = (d ^ a') <<< 16 + c' = c + d' + b' = (b ^ c') <<< 12 + a'' = a' + b' + d'' = (d' ^ a'') <<< 8 + c'' = c' + d'' + b'' = (b' ^ c'') <<< 7 +``` + +Where "+" denotes integer addition without carry, "^" denotes a +bitwise XOR, and "<<< n" denotes an n-bit left rotation (towards the +high bits). + +For example, let's see the add, XOR and roll operations from the +first two lines with sample numbers: + + * b = 0x01020304 + * a = 0x11111111 + * d = 0x01234567 + * a = a + b = 0x11111111 + 0x01020304 = 0x12131415 + * d = d ^ a = 0x01234567 ^ 0x12131415 = 0x13305172 + * d = d<<<16 = 0x51721330 + +### Test Vector for the ChaCha Quarter Round + +For a test vector, we will use the same numbers as in the example, +adding something random for c. + +After running a Quarter Round on these 4 numbers, we get these: + +```cryptol +property ChaChaQuarterround_passes_test = + ChaChaQuarterround [ 0x11111111 // a + , 0x01020304 // b + , 0x9b8d6f43 // c + , 0x01234567 // d + ] + == + [ 0xea2a92f4 + , 0xcb1cf8ce + , 0x4581472e + , 0x5881c4bb + ] +``` + +## A Quarter Round on the ChaCha State + +The ChaCha state does not have 4 integer numbers, but 16. So the +quarter round operation works on only 4 of them - hence the name. +Each quarter round operates on 4 pre-determined numbers in the ChaCha +state. We will denote by QUATERROUND(x,y,z,w) a quarter-round +operation on the numbers at indexes x, y, z, and w of the ChaCha +state when viewed as a vector. For example, if we apply +QUARTERROUND(1,5,9,13) to a state, this means running the quarter +round operation on the elements marked with an asterisk, while +leaving the others alone: + +```example +0 *a 2 3 +4 *b 6 7 +8 *c 10 11 +12 *d 14 15 +``` + +Note that this run of quarter round is part of what is called a +"column round". + +### Test Vector for the Quarter Round on the ChaCha state + +For a test vector, we will use a ChaCha state that was generated +randomly: + +Sample ChaCha State + +```example + 879531e0 c5ecf37d 516461b1 c9a62f8a + 44c20ef3 3390af7f d9fc690b 2a5f714c + 53372767 b00a5631 974c541a 359e9963 + 5c971061 3d631689 2098d9d6 91dbd320 +``` +We will apply the QUARTERROUND(2,7,8,13) operation to this state. +For obvious reasons, this one is part of what is called a "diagonal +round": + +After applying QUARTERROUND(2,7,8,13) + +```example + 879531e0 c5ecf37d bdb886dc c9a62f8a + 44c20ef3 3390af7f d9fc690b cfacafd2 + e46bea80 b00a5631 974c541a 359e9963 + 5c971061 ccc07c79 2098d9d6 91dbd320 +``` + +Note that only the numbers in positions 2, 7, 8, and 13 changed. + +In the Cryptol implementation of ChaCha20, the ChaChaQuarterround is called on four elements at a time, +and there is no destructive state modification, so it would be artificial to reproduce the +above example of the partially-destructively modified matrix. Instead, we show the output of +calling ChaChaQuarterround on the diagonal elements identified above: + +```cryptol +property ChaChaQuarterround_passes_column_test = + ChaChaQuarterround [ 0x516461b1 // a + , 0x2a5f714c // b + , 0x53372767 // c + , 0x3d631689 // d + ] + == + [ 0xbdb886dc + , 0xcfacafd2 + , 0xe46bea80 + , 0xccc07c79 + ] +``` + +## The ChaCha20 block Function + +The ChaCha block function transforms a ChaCha state by running +multiple quarter rounds. + +The inputs to ChaCha20 are: + + * A 256-bit key, treated as a concatenation of 8 32-bit little-endian integers. + * A 96-bit nonce, treated as a concatenation of 3 32-bit little-endian integers. + * A 32-bit block count parameter, treated as a 32-bit little-endian integer. + +The output is 64 random-looking bytes. + +```cryptol +ChaCha20Block : ChaChaKey -> [96] -> [32] -> ChaChaState +``` + +The ChaCha algorithm described here uses a 256-bit key. The original +algorithm also specified 128-bit keys and 8- and 12-round variants, +but these are out of scope for this document. In this section we +describe the ChaCha block function. + +```cryptol +type ChaChaKey = [256] +``` + +Note also that the original ChaCha had a 64-bit nonce and 64-bit +block count. We have modified this here to be more consistent with +recommendations in section 3.2 of [RFC5116]. This limits the use of +a single (key,nonce) combination to 2^32 blocks, or 256 GB, but that +is enough for most uses. In cases where a single key is used by +multiple senders, it is important to make sure that they don't use +the same nonces. This can be assured by partitioning the nonce space +so that the first 32 bits are unique per sender, while the other 64 +bits come from a counter. + +The ChaCha20 state is initialized as follows: + + * The first 4 words (0-3) are constants: 0x61707865, 0x3320646e, + 0x79622d32, 0x6b206574. + +```cryptol +FirstRow = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] +property FirstRow_correct = groupBy`{8}(join [ littleendian (split w) + | w <- FirstRow ]) + == "expand 32-byte k" +``` + + * The next 8 words (4-11) are taken from the 256-bit key by + reading the bytes in little-endian order, in 4-byte chunks. + +```cryptol +KeyToRows : ChaChaKey -> [8][32] +KeyToRows key = [littleendian (split words) | words <- (split key)] +``` + + * Word 12 is a block counter. Since each block is 64-byte, + a 32-bit word is enough for 256 Gigabytes of data. + * Words 13-15 are a nonce, which should not be repeated for the same + key. The 13th word is the first 32 bits of the input nonce taken + as a little-endian integer, while the 15th word is the last 32 + bits. + +```verbatim +Initial state structure: + +cccccccc cccccccc cccccccc cccccccc +kkkkkkkk kkkkkkkk kkkkkkkk kkkkkkkk +kkkkkkkk kkkkkkkk kkkkkkkk kkkkkkkk +bbbbbbbb nnnnnnnn nnnnnnnn nnnnnnnn + +c=constant k=key b=blockcount n=nonce +``` + +```cryptol +NonceToRow : [96] -> [32] -> [4][32] +NonceToRow n i = [i] # [ littleendian (split words) | words <- groupBy`{32} n ] +``` + +```cryptol +BuildState : ChaChaKey -> [96] -> [32] -> [16][32] +BuildState key nonce i = split (join (FirstRow # KeyToRows key # NonceToRow nonce i)) +``` + +ChaCha20 runs 20 rounds, alternating between "column" and "diagonal" +rounds. Each round is 4 quarter-rounds, and they are run as follows. +Rounds 1-4 are part of the "column" round, while 5-8 are part of the +"diagonal" round: + +```cryptol +columns = [ 0, 4, 8, 12, // round 1 - column round + 1, 5, 9, 13, // round 2 + 2, 6, 10, 14, // round 3 + 3, 7, 11, 15 ] // round 4 +diags = [ 0, 5, 10, 15, // round 5 - diagonal round + 1, 6, 11, 12, // round 6 + 2, 7, 8, 13, // round 7 + 3, 4, 9, 14 ] // round 8 +``` + +The Cryptol pattern of using the `@@` operator on permutations of the indices of +the matrix creates a new matrix that consists of rows that correspond to the +quarter-round calls. To restore the element-indices to their original ordering, +after each application we perform the inverse permutation. Since the column +round is just a square matrix transposition, it inverts itself, but the +diagonal round needs to have an inverse permutation calculated, which we do +here: + +```cryptol +inversePermutation (perms:[a+1]b) = [ indexOf i perms | i <- [ 0 .. a ] ] +invDiags = inversePermutation diags +invCols = inversePermutation columns // which happens to be the same as columns + +ChaChaTwoRounds (xs:ChaChaState) = xs'' where + xs' = join [ChaChaQuarterround x | x <- groupBy`{4}(xs@@columns) ] @@ invCols + xs'' = (join [ChaChaQuarterround x | x <- groupBy`{4}(xs'@@diags ) ]) @@ invDiags + +ChaCha : ChaChaState -> [8] -> ChaChaState +ChaCha s n = chain@n where + chain = [s] # [ ChaChaTwoRounds ci | ci <- chain | i <- [0 .. 9] ] +``` + +At the end of 20 rounds, the original input words are added to the +output words, and the result is serialized by sequencing the words +one-by-one in little-endian order. + +```cryptol +// ChaCha20Block : ChaChaKey -> [96] -> [32] -> ChaChaState (repeated from above) +ChaCha20Block key nonce i = (ChaCha initialState 10) + initialState where + initialState = BuildState key nonce i +``` + +### Test Vector for the ChaCha20 Block Function + +For a test vector, we will use the following inputs to the ChaCha20 +block function: + +```cryptol +TestKey : ChaChaKey +TestKey = join (parseHexString + ( "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:" # + "14:15:16:17:18:19:1a:1b:1c:1d:1e:1f.") ) +``` + +The key is a sequence of octets with no particular structure before we copy it +into the ChaCha state. + +```cryptol +TestNonce : [96] +TestNonce = join (parseHexString "00:00:00:09:00:00:00:4a:00:00:00:00.") +``` + +After setting up the ChaCha state, it looks like this: + +ChaCha State with the key set up. + +```cryptol +TestState = BuildState TestKey TestNonce 1 + +property BuildState_correct = TestState == [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, + 0x00000001, 0x09000000, 0x4a000000, 0x00000000 ] +``` + +After running 20 rounds (10 column rounds interleaved with 10 +diagonal rounds), the ChaCha state looks like this: + +ChaCha State after 20 rounds + +```cryptol +ChaCha20_state1 = [ + 0x837778ab, 0xe238d763, 0xa67ae21e, 0x5950bb2f, + 0xc4f2d0c7, 0xfc62bb2f, 0x8fa018fc, 0x3f5ec7b7, + 0x335271c2, 0xf29489f3, 0xeabda8fc, 0x82e46ebd, + 0xd19c12b4, 0xb04e16de, 0x9e83d0cb, 0x4e3c50a2 + ] + +property ChaChaStateAfter20_correct = ChaCha TestState 10 == ChaCha20_state1 +``` + +Finally we add the original state to the result (simple vector or +matrix addition), giving this: + +ChaCha State at the end of the ChaCha20 operation + +```cryptol +ChaCha20_block_1 = [ + 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, + 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3, + 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, + 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 + ] + +property ChaCha20_test1 = ChaCha20Block TestKey TestNonce 1 == ChaCha20_block_1 +``` + +## The ChaCha20 encryption algorithm + +ChaCha20 is a stream cipher designed by D. J. Bernstein. It is a +refinement of the Salsa20 algorithm, and uses a 256-bit key. + +ChaCha20 successively calls the ChaCha20 block function, with the +same key and nonce, and with successively increasing block counter +parameters. ChaCha20 then serializes the resulting state by writing +the numbers in little-endian order, creating a key-stream block. +Concatenating the key-stream blocks from the successive blocks forms +a key stream, which is then XOR-ed with the plaintext. +Alternatively, each key-stream block can be XOR-ed with a plaintext +block before proceeding to create the next block, saving some memory. + +There is no requirement for the plaintext to be an integral multiple +of 512-bits. If there is extra keystream from the last block, it is +discarded. Specific protocols MAY require that the plaintext and +ciphertext have certain length. Such protocols need to specify how +the plaintext is padded, and how much padding it receives. + +The inputs to ChaCha20 are: + + * A 256-bit key + * A 32-bit initial counter. This can be set to any number, but will + usually be zero or one. It makes sense to use 1 if we use the + zero block for something else, such as generating a one-time + authenticator key as part of an AEAD algorithm. + * A 96-bit nonce. In some protocols, this is known as the + Initialization Vector. + * an arbitrary-length plaintext + +The output is an encrypted message of the same length. + +```cryptol +// TODO: reorder args below, and get rid of this wrapper +ChaCha20Encrypt : {a} (fin a) => ChaChaKey -> [32] -> [96] -> [a][8] -> [a][8] +ChaCha20Encrypt k i n msg = ChaCha20EncryptBytes msg k n i + +ChaCha20EncryptBytes msg k n i= [ m ^ kb | m <- msg | kb <- keystream ] where + keystream = groupBy`{8}(join (join (ChaCha20ExpandKey k n i))) + +ChaCha20ExpandKey : ChaChaKey -> [96] -> [32] -> [inf]ChaChaState +ChaCha20ExpandKey k n i = [ ToLittleEndian (ChaCha20Block k n j) + | j <- ([i ...]:[_][32]) + ] + +``` + +Decryption is done in the same way. The ChaCha20 block function is +used to expand the key into a key stream, which is XOR-ed with the +ciphertext giving back the plaintext. + +```cryptol +ChaCha20DecryptBytes = ChaCha20EncryptBytes +``` + +### Example and Test Vector for the ChaCha20 Cipher + +For a test vector, we will use the following inputs to the ChaCha20 +block function: + +```cryptol +Sunscreen_Key = join (parseHexString + ( "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:" + # "14:15:16:17:18:19:1a:1b:1c:1d:1e:1f." + ) ) + +Sunscreen_Nonce = join (parseHexString "00:00:00:00:00:00:00:4a:00:00:00:00.") +Sunscreen_Initial_Counter = 1 +``` + +We use the following for the plaintext. It was chosen to be long +enough to require more than one block, but not so long that it would +make this example cumbersome (so, less than 3 blocks): + +Plaintext Sunscreen: + +```cryptol +Plaintext_Sunscreen = "Ladies and Gentlemen of the class of '99: " # + "If I could offer you only one tip for the " # + "future, sunscreen would be it." +``` + +The following figure shows 4 ChaCha state matrices: + + 1. First block as it is set up. + 1. Second block as it is set up. Note that these blocks are only + two bits apart - only the counter in position 12 is different. + 1. Third block is the first block after the ChaCha20 block + operation. + 1. Final block is the second block after the ChaCha20 block + operation was applied. + +After that, we show the keystream. + +First block setup: + +```cryptol +Sunscreen_State1 = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, + 0x00000001, 0x00000000, 0x4a000000, 0x00000000 + ] + +property SunscreenBuildState_correct = + BuildState Sunscreen_Key Sunscreen_Nonce 1 == Sunscreen_State1 +``` + +Second block setup: + +```cryptol +Sunscreen_State2 = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, + 0x00000002, 0x00000000, 0x4a000000, 0x00000000 + ] + +property SunscreenBuildState2_correct = + BuildState Sunscreen_Key Sunscreen_Nonce 2 == Sunscreen_State2 +``` + +First block after block operation: + +```cryptol +SunscreenAfterBlock1 = [ + 0xf3514f22, 0xe1d91b40, 0x6f27de2f, 0xed1d63b8, + 0x821f138c, 0xe2062c3d, 0xecca4f7e, 0x78cff39e, + 0xa30a3b8a, 0x920a6072, 0xcd7479b5, 0x34932bed, + 0x40ba4c79, 0xcd343ec6, 0x4c2c21ea, 0xb7417df0 + ] + +property SunscreenBlock1_correct = + ChaCha20Block Sunscreen_Key Sunscreen_Nonce 1 == SunscreenAfterBlock1 +``` + +Second block after block operation: + +```cryptol +SunscreenAfterBlock2 = [ + 0x9f74a669, 0x410f633f, 0x28feca22, 0x7ec44dec, + 0x6d34d426, 0x738cb970, 0x3ac5e9f3, 0x45590cc4, + 0xda6e8b39, 0x892c831a, 0xcdea67c1, 0x2b7e1d90, + 0x037463f3, 0xa11a2073, 0xe8bcfb88, 0xedc49139 + ] + +property SunscreenBlock2_correct = + ChaCha20Block Sunscreen_Key Sunscreen_Nonce 2 == SunscreenAfterBlock2 +``` + +Keystream: + +```cryptol +SunscreenKeystream = (parseHexString + ( "22:4f:51:f3:40:1b:d9:e1:2f:de:27:6f:b8:63:1d:ed:8c:13:1f:82:3d:2c:06:" + # "e2:7e:4f:ca:ec:9e:f3:cf:78:8a:3b:0a:a3:72:60:0a:92:b5:79:74:cd:ed:2b:" + # "93:34:79:4c:ba:40:c6:3e:34:cd:ea:21:2c:4c:f0:7d:41:b7:69:a6:74:9f:3f:" + # "63:0f:41:22:ca:fe:28:ec:4d:c4:7e:26:d4:34:6d:70:b9:8c:73:f3:e9:c5:3a:" + # "c4:0c:59:45:39:8b:6e:da:1a:83:2c:89:c1:67:ea:cd:90:1d:7e:2b:f3:63." + ) ) + +property SunscreenKeystream_correct (skref:[skwidth][8]) = + take`{skwidth} + (groupBy`{8} (join (join(ChaCha20ExpandKey + Sunscreen_Key Sunscreen_Nonce 1)))) == skref +``` + +Finally, we XOR the Keystream with the plaintext, yielding the Ciphertext: + +```cryptol +Ciphertext_Sunscreen = + [0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, + 0x28, 0xdd, 0x0d, 0x69, 0x81, 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, + 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, 0xf9, + 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, + 0xcd, 0x62, 0xb3, 0x57, 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, + 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, 0x07, 0xca, + 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, 0x56, 0xa3, 0x8e, 0x08, 0x8a, + 0x22, 0xb6, 0x5e, 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, + 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, 0x5a, 0xf9, 0x0b, + 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, + 0x5e, 0x42, 0x87, 0x4d] + +property ChaCha_encrypt_sunscreen_correct = + ChaCha20EncryptBytes Plaintext_Sunscreen Sunscreen_Key Sunscreen_Nonce 1 + == Ciphertext_Sunscreen + +property Sunscreen_decrypt_correct = + ChaCha20DecryptBytes Ciphertext_Sunscreen Sunscreen_Key Sunscreen_Nonce 1 + == Plaintext_Sunscreen +``` + +# The Poly1305 algorithm + +Poly1305 is a one-time authenticator designed by D. J. Bernstein. +Poly1305 takes a 32-byte one-time key and a message and produces a +16-byte tag. + +The original article ([poly1305]) is entitled "The Poly1305-AES +message-authentication code", and the MAC function there requires a +128-bit AES key, a 128-bit "additional key", and a 128-bit (non- +secret) nonce. AES is used there for encrypting the nonce, so as to +get a unique (and secret) 128-bit string, but as the paper states, +"There is nothing special about AES here. One can replace AES with +an arbitrary keyed function from an arbitrary set of nonces to 16- +byte strings." + +Regardless of how the key is generated, the key is partitioned into +two parts, called "r" and "s". The pair ``(r,s)`` should be unique, and +MUST be unpredictable for each invocation (that is why it was +originally obtained by encrypting a nonce), while "r" MAY be +constant, but needs to be modified as follows before being used: ("r" +is treated as a 16-octet little-endian number): + + * r[3], r[7], r[11], and r[15] are required to have their top four + bits clear (be smaller than 16) + +```cryptol +Om = 15 // odd masks - for 3, 7, 11 & 15 +``` + * r[4], r[8], and r[12] are required to have their bottom two bits + clear (be divisible by 4) + +The following Cryptol code clamps "r" to be appropriate: + +```cryptol +Em = 252 // even masks - for 4, 8 & 12 +nm = 255 // no mask + +PolyMasks : [16][8] // mask indices +PolyMasks = [ nm, nm, nm, Om, // 0-3 + Em, nm, nm, Om, // 4-7 + Em, nm, nm, Om, // 8-11 + Em, nm, nm, Om ] // 12-15 + +Poly1305_clamp : [16][8] -> [16][8] +Poly1305_clamp r = [ re && mask | re <- r | mask <- PolyMasks ] +``` + +The "s" should be unpredictable, but it is perfectly acceptable to +generate both "r" and "s" uniquely each time. Because each of them +is 128-bit, pseudo-randomly generating them (see "Generating the +Poly1305 key using ChaCha20") is also acceptable. + +The inputs to Poly1305 are: + + * A 256-bit one-time key + * An arbitrary length message (comprised of `floorBlocks` 16-byte blocks, + and `rem` bytes left over) + +The output is a 128-bit tag. + +```cryptol +Poly1305 : {m, floorBlocks, rem} (fin m, floorBlocks == m/16, rem == m - floorBlocks*16) + => [256] -> [m][8] -> [16][8] +``` + +Set the constant prime "P" be 2^130-5. + +```cryptol +P : [136] +P = 2^^130 - 5 +``` + +First, the "r" value should be clamped. + +```cryptol +Poly1305 key msg = result where + [ru, su] = split key + r : [136] // internal arithmetic on (128+8)-bit numbers + r = littleendian ((Poly1305_clamp (split ru)) # [0x00]) + s = littleendian ((split su) # [0x00]) +``` + +Next, divide the message into 16-byte blocks. The last block might be shorter: + + * Read each block as a little-endian number. + * Add one bit beyond the number of octets. For a 16-byte block this + is equivalent to adding 2^128 to the number. For the shorter + block it can be 2^120, 2^112, or any power of two that is evenly + divisible by 8, all the way down to 2^8. + +```cryptol + // pad all the blocks uniformly (we'll handle the final block later) + paddedBlocks = [ 0x01 # (littleendian block) + | block <- groupBy`{16}(msg # (zero:[inf][8])) ] +``` + * If the block is not 17 bytes long (the last block), then left-pad it with + zeros. This is meaningless if you're treating it them as numbers. + +```cryptol + lastBlock : [136] + lastBlock = zero # 0x01 # (littleendian (drop`{16*floorBlocks} msg)) +``` + + * Add the current block to the accumulator. + * Multiply by "r" + * Set the accumulator to the result modulo p. To summarize: + ``accum[i+1] = ((accum[i]+block)*r) % p``. + +```cryptol + accum:[_][136] + accum = [zero:[136]] # [ computeElt a b r P | a <- accum | b <- paddedBlocks ] + // ^ the accumulator starts at zero +``` + + * If the block division leaves no remainder, the last value of the accumulator is good + otherwise compute the special-case padded block, and compute the final value of the accumulator + +```cryptol + lastAccum : [136] + lastAccum = if `rem == 0 + then accum@`floorBlocks + else computeElt (accum@`floorBlocks) lastBlock r P +``` + +Finally, the value of the secret key "s" is added to the accumulator, +and the 128 least significant bits are serialized in little-endian +order to form the tag. + +```cryptol + result = reverse (groupBy`{8} (drop`{8}(lastAccum + s))) + +// Compute ((a + b) * r ) % P being pedantic about bit-widths +computeElt : [136] -> [136] -> [136] -> [136] -> [136] +computeElt a b r p = (drop`{137}bigResult) where + bigResult : [273] + aPlusB : [137] + aPlusB = (0b0#a) + (0b0#b) // make room for carry + timesR : [273] + timesR = ((zero:[136])#aPlusB) * ((zero:[137])#r) // [a]*[b]=[a+b] + bigResult = timesR % ((zero:[137])#p) + +``` + +### Poly1305 Example and Test Vector + +For our example, we will dispense with generating the one-time key +using AES, and assume that we got the following keying material: + + * Key Material: 85:d6:be:78:57:55:6d:33:7f:44:52:fe:42:d5:06:a8:01: + 03:80:8a:fb:0d:b2:fd:4a:bf:f6:af:41:49:f5:1b + +```cryptol +Poly1305TestKey = join (parseHexString + ( "85:d6:be:78:57:55:6d:33:7f:44:52:fe:42:d5:06:a8:01:" + # "03:80:8a:fb:0d:b2:fd:4a:bf:f6:af:41:49:f5:1b." + ) ) +``` + + * s as an octet string: 01:03:80:8a:fb:0d:b2:fd:4a:bf:f6:af:41:49:f5:1b + * s as a 128-bit number: 1bf54941aff6bf4afdb20dfb8a800301 + +```cryptol +Poly1305Test_s = parseHexString + "01:03:80:8a:fb:0d:b2:fd:4a:bf:f6:af:41:49:f5:1b." +Poly1305Test_sbits = join (reverse Poly1305Test_s) + +property poly1306Sokay = Poly1305Test_sbits == 0x1bf54941aff6bf4afdb20dfb8a800301 +``` + + +```cryptol +Poly1305TestMessage = "Cryptographic Forum Research Group" +``` + + * r before clamping: 85:d6:be:78:57:55:6d:33:7f:44:52:fe:42:d5:06:a8 + * Clamped r as a number: 806d5400e52447c036d555408bed685. + +Since Poly1305 works in 16-byte chunks, the 34-byte message divides +into 3 blocks. In the following calculation, "Acc" denotes the +accumulator and "Block" the current block: + +Here we define a Cryptol function that returns all of the intermediate +values of the accumulator: + +```cryptol +// TODO: refactor the Poly function in terms of this AccumBlocks +// challenge: doing so while maintaining the clean literate correspondence with the spec +AccumBlocks : {m, floorBlocks, rem} (fin m, floorBlocks == m/16, rem == m - floorBlocks*16) + => [256] -> [m][8] -> ([_][136], [136]) + +AccumBlocks key msg = (accum, lastAccum) where + [ru, su] = split key + r : [136] // internal arithmetic on (128+8)-bit numbers + r = littleendian ((Poly1305_clamp (split ru)) # [0x00]) + s = littleendian ((split su) # [0x00]) + // pad all the blocks uniformly (we'll handle the final block later) + paddedBlocks = [ 0x01 # (littleendian block) + | block <- groupBy`{16}(msg # (zero:[inf][8])) ] + lastBlock : [136] + lastBlock = zero # 0x01 # (littleendian (drop`{16*floorBlocks} msg)) + accum:[_][136] + accum = [zero:[136]] # [ computeElt a b r P | a <- accum | b <- paddedBlocks ] + // ^ the accumulator starts at zero + lastAccum : [136] + lastAccum = if `rem == 0 + then accum@`floorBlocks + else computeElt (accum@`floorBlocks) lastBlock r P + +``` + +```example +Block #1 + +Acc = 00 +Block = 6f4620636968706172676f7470797243 +Block with 0x01 byte = 016f4620636968706172676f7470797243 +Acc + block = 016f4620636968706172676f7470797243 +(Acc+Block) * r = + b83fe991ca66800489155dcd69e8426ba2779453994ac90ed284034da565ecf +Acc = ((Acc+Block)*r) % P = 2c88c77849d64ae9147ddeb88e69c83fc + +Block #2 + +Acc = 2c88c77849d64ae9147ddeb88e69c83fc +Block = 6f7247206863726165736552206d7572 +Block with 0x01 byte = 016f7247206863726165736552206d7572 +Acc + block = 437febea505c820f2ad5150db0709f96e +(Acc+Block) * r = + 21dcc992d0c659ba4036f65bb7f88562ae59b32c2b3b8f7efc8b00f78e548a26 +Acc = ((Acc+Block)*r) % P = 2d8adaf23b0337fa7cccfb4ea344b30de + +Last Block + +Acc = 2d8adaf23b0337fa7cccfb4ea344b30de +Block = 7075 +Block with 0x01 byte = 017075 +Acc + block = 2d8adaf23b0337fa7cccfb4ea344ca153 +(Acc + Block) * r = + 16d8e08a0f3fe1de4fe4a15486aca7a270a29f1e6c849221e4a6798b8e45321f +((Acc + Block) * r) % P = 28d31b7caff946c77c8844335369d03a7 +``` + +```cryptol +property polyBlocksOK = + (blocks @ 1 == 0x02c88c77849d64ae9147ddeb88e69c83fc) && + (blocks @ 2 == 0x02d8adaf23b0337fa7cccfb4ea344b30de) && + (lastBlock == 0x028d31b7caff946c77c8844335369d03a7) where + (blocks, lastBlock) = AccumBlocks Poly1305TestKey Poly1305TestMessage +``` + +Adding s we get this number, and serialize if to get the tag: + +Acc + s = 2a927010caf8b2bc2c6365130c11d06a8 + +Tag: a8:06:1d:c1:30:51:36:c6:c2:2b:8b:af:0c:01:27:a9 + +```cryptol +// Putting it all together and testing: + +Poly1305TestTag = "a8:06:1d:c1:30:51:36:c6:c2:2b:8b:af:0c:01:27:a9." + +property Poly1305_passes_test = Poly1305 Poly1305TestKey Poly1305TestMessage == + parseHexString Poly1305TestTag +``` + +## Generating the Poly1305 key using ChaCha20 + +As said in the "Poly 1305 Algorithm" section, it is acceptable to generate +the one-time Poly1305 pseudo-randomly. This section proposes such a method. + +To generate such a key pair (r,s), we will use the ChaCha20 block +function described in Section 2.3. This assumes that we have a 256- +bit session key for the MAC function, such as SK_ai and SK_ar in +IKEv2 ([RFC5996]), the integrity key in ESP and AH, or the +client_write_MAC_key and server_write_MAC_key in TLS. Any document +that specifies the use of Poly1305 as a MAC algorithm for some +protocol must specify that 256 bits are allocated for the integrity +key. Note that in the AEAD construction defined in Section 2.8, the +same key is used for encryption and key generation, so the use of +SK_a* or *_write_MAC_key is only for stand-alone Poly1305. + +The method is to call the block function with the following +parameters: + + * The 256-bit session integrity key is used as the ChaCha20 key. + * The block counter is set to zero. + * The protocol will specify a 96-bit or 64-bit nonce. This MUST be + unique per invocation with the same key, so it MUST NOT be + randomly generated. A counter is a good way to implement this, + but other methods, such as an LFSR are also acceptable. ChaCha20 + as specified here requires a 96-bit nonce. So if the provided + nonce is only 64-bit, then the first 32 bits of the nonce will be + set to a constant number. This will usually be zero, but for + protocols with multiple senders it may be different for each + sender, but should be the same for all invocations of the function + with the same key by a particular sender. + +After running the block function, we have a 512-bit state. We take +the first 256 bits or the serialized state, and use those as the one- +time Poly1305 key: The first 128 bits are clamped, and form "r", +while the next 128 bits become "s". The other 256 bits are +discarded. + +Note that while many protocols have provisions for a nonce for +encryption algorithms (often called Initialization Vectors, or IVs), +they usually don't have such a provision for the MAC function. In +that case the per-invocation nonce will have to come from somewhere +else, such as a message counter. + +### Poly1305 Key Generation Test Vector + +For this example, we'll set: + +```cryptol +PolyKeyTest = join (parseHexString ( + "80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f " # + "90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f " + )) + +PolyNonceTest : [96] +PolyNonceTest = join ( + parseHexString ("00 00 00 00 00 01 02 03 04 05 06 07 ")) +``` + +The ChaCha state set up with key, nonce, and block counter zero: + +```cryptol +PolyBuildState_testVector = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x83828180, 0x87868584, 0x8b8a8988, 0x8f8e8d8c, + 0x93929190, 0x97969594, 0x9b9a9998, 0x9f9e9d9c, + 0x00000000, 0x00000000, 0x03020100, 0x07060504 ] + +property PolyBuildState_correct = BuildState PolyKeyTest PolyNonceTest 0 + == PolyBuildState_testVector +``` + +The ChaCha state after 20 rounds: + +```cryptol +PolyChaChaState_testVector = [ + 0x8ba0d58a, 0xcc815f90, 0x27405081, 0x7194b24a, + 0x37b633a8, 0xa50dfde3, 0xe2b8db08, 0x46a6d1fd, + 0x7da03782, 0x9183a233, 0x148ad271, 0xb46773d1, + 0x3cc1875a, 0x8607def1, 0xca5c3086, 0x7085eb87 ] + +property PolyChaCha_correct = ChaCha20Block PolyKeyTest PolyNonceTest 0 == + PolyChaChaState_testVector +``` + +And that output is also the 32-byte one-time key used for Poly1305. + +```cryptol +PolyOutput = join (parseHexString ( + "8a d5 a0 8b 90 5f 81 cc 81 50 40 27 4a b2 94 71 " # + "a8 33 b6 37 e3 fd 0d a5 08 db b8 e2 fd d1 a6 46 ")) + +GeneratePolyKeyUsingChaCha k n i = join [littleendian (groupBy`{8}b) + | b <- take `{8}(ChaCha20Block k n i) ] + +property Poly_passes_test = GeneratePolyKeyUsingChaCha PolyKeyTest PolyNonceTest 0 == PolyOutput +``` + +## A Pseudo-Random Function for ChaCha/Poly-1305 based Crypto Suites + +Some protocols such as IKEv2([RFC5996]) require a Pseudo-Random +Function (PRF), mostly for key derivation. In the IKEv2 definition, +a PRF is a function that accepts a variable-length key and a +variable-length input, and returns a fixed-length output. This +section does not specify such a function. + +Poly-1305 is an obvious choice, because MAC functions are often used +as PRFs. However, Poly-1305 prohibits using the same key twice, +whereas the PRF in IKEv2 is used multiple times with the same key. +Adding a nonce or a counter to Poly-1305 can solve this issue, much +as we do when using this function as a MAC, but that would require +changing the interface for the PRF function. + +Chacha20 could be used as a key-derivation function, by generating an +arbitrarily long keystream. However, that is not what protocols such +as IKEv2 require. + +For this reason, this document does not specify a PRF, and recommends +that crypto suites use some other PRF such as PRF_HMAC_SHA2_256 +(section 2.1.2 of [RFC4868]) + +## AEAD Construction + +AEAD_CHACHA20-POLY1305 is an authenticated encryption with additional +data algorithm. The inputs to AEAD_CHACHA20-POLY1305 are: + + * A 256-bit key + * A 96-bit nonce - different for each invocation with the same key. + * An arbitrary length plaintext (fewer than 2^64 bytes) + * Arbitrary length additional data (AAD) (fewer than 2^64 bytes) + +```cryptol +AEAD_CHACHA20_POLY1305 : {m, n} + (fin m, 64 >= width m + ,fin n, 64 >= width n ) + => [256] -> [96] -> [m][8] -> [n][8] + -> [m+16][8] + +AEAD_CHACHA20_POLY1305 k nonce p aad = (ct # tag) where +``` + +Some protocols may have unique per-invocation inputs that are not 96- +bit in length. For example, IPsec may specify a 64-bit nonce. In +such a case, it is up to the protocol document to define how to +transform the protocol nonce into a 96-bit nonce, for example by +concatenating a constant value. + +The ChaCha20 and Poly1305 primitives are combined into an AEAD that +takes a 256-bit key and 96-bit nonce as follows: + + * First, a Poly1305 one-time key is generated from the 256-bit key + and nonce using the procedure described in "Generating the Poly1305 key using ChaCha20". + +```cryptol + PolyKey = GeneratePolyKeyUsingChaCha k nonce 0 +``` + + * Next, the ChaCha20 encryption function is called to encrypt the + plaintext, using the input key and nonce, and with the initial + counter set to 1. + +```cryptol + ct = ChaCha20EncryptBytes p k nonce 1 +``` + + * Finally, the Poly1305 function is called with the Poly1305 key + calculated above, and a message constructed as a concatenation of + the following: + * The AAD + * padding1 - the padding is up to 15 zero bytes, and it brings + the total length so far to an integral multiple of 16. If the + length of the AAD was already an integral multiple of 16 bytes, + this field is zero-length. + * The ciphertext + * padding2 - the padding is up to 15 zero bytes, and it brings + the total length so far to an integral multiple of 16. If the + length of the ciphertext was already an integral multiple of 16 + bytes, this field is zero-length. + * The length of the additional data in octets (as a 64-bit + little-endian integer). + * The length of the ciphertext in octets (as a 64-bit little- + endian integer). + +```cryptol + ptlen : [8][8] + ptlen = groupBy`{8}(littleendian (groupBy`{8}(`m:[64]))) + adlen : [8][8] + adlen = groupBy`{8}(littleendian (groupBy`{8}(`n:[64]))) + // compute padding + type p1 = (16-m%16)%16 + type p2 = (16-n%16)%16 + tag : [16][8] + tag = Poly1305 PolyKey (aad # (zero:[p1][8]) # ct # (zero:[p2][8]) # adlen # ptlen) +``` + +The output from the AEAD is twofold: + + * A ciphertext of the same length as the plaintext. + * A 128-bit tag, which is the output of the Poly1305 function. + +Decryption is pretty much the same thing. + +```cryptol + +AEAD_CHACHA20_POLY1305_DECRYPT : {m, n} (fin m, fin n + ,64 >= width m, 64 >= width n) + => [256] -> [96] + -> [m+16][8] -> [n][8] + -> ([m][8], Bit) + +AEAD_CHACHA20_POLY1305_DECRYPT k nonce ct ad = (pt, valid) where + inTag = drop`{m}ct + inCt = take`{m}ct + PolyKey = GeneratePolyKeyUsingChaCha k nonce 0 + pt = ChaCha20DecryptBytes inCt k nonce 1 + ptlen : [8][8] + ptlen = groupBy`{8}(littleendian (groupBy`{8}(`m:[64]))) + adlen : [8][8] + adlen = groupBy`{8}(littleendian (groupBy`{8}(`n:[64]))) + // compute padding + type p1 = (16-m%16)%16 + type p2 = (16-n%16)%16 + tag = Poly1305 PolyKey (ad # (zero:[p1][8]) # inCt # (zero:[p2][8]) # adlen # ptlen) + valid = tag == inTag +``` + +A few notes about this design: + + 1. The amount of encrypted data possible in a single invocation is + 2^32-1 blocks of 64 bytes each, because of the size of the block + counter field in the ChaCha20 block function. This gives a total + of 247,877,906,880 bytes, or nearly 256 GB. This should be + enough for traffic protocols such as IPsec and TLS, but may be + too small for file and/or disk encryption. For such uses, we can + return to the original design, reduce the nonce to 64 bits, and + use the integer at position 13 as the top 32 bits of a 64-bit + block counter, increasing the total message size to over a + million petabytes (1,180,591,620,717,411,303,360 bytes to be + exact). + + 1. Despite the previous item, the ciphertext length field in the + construction of the buffer on which Poly1305 runs limits the + ciphertext (and hence, the plaintext) size to 2^64 bytes, or + sixteen thousand petabytes (18,446,744,073,709,551,616 bytes to + be exact). + +### Example and Test Vector for AEAD_CHACHA20-POLY1305 + +For a test vector, we will use the following inputs to the +AEAD_CHACHA20-POLY1305 function: + +Plaintext: + +```cryptol +AeadPt = "Ladies and Gentlemen of the class of '99: " # + "If I could offer you only one tip for " # + "the future, sunscreen would be it." + +AeadAAD = parseHexString "50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7 " + +AeadKey = join (parseHexString ( + "80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f " # + "90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f " )) + + +AeadIV = join [ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 ] + +AeadC = join [0x07, 0x00, 0x00, 0x00] + +AeadNonce = AeadC # AeadIV +``` + +32-bit fixed-common part: + +```cryptol +AeadCT = ChaCha20EncryptBytes AeadPt AeadKey AeadNonce 1 + +type p1width = 4 +type p2width = 14 + +AeadConstruction AAD CT = (AeadAAD # padding1 # AeadCT # padding2 # ADleLen # CTleLen) where + padding1 = zero:[p1width][8] + padding2 = zero:[p2width][8] + +AeadPolyKey = GeneratePolyKeyUsingChaCha AeadKey (AeadC # AeadIV) 0 + +ADleLen : [8][8] +ADleLen = groupBy`{8}(littleendian (groupBy`{8}((width AeadAAD):[64]))) + +CTleLen : [8][8] +CTleLen = groupBy`{8}(littleendian (groupBy`{8}((width AeadCT):[64]))) + +AeadTag = Poly1305 AeadPolyKey (AeadConstruction AeadAAD AeadCT) +``` + +Set up for generating poly1305 one-time key (sender id=7): + +```cryptol +AeadPolyOneTimeKey_testVector = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x83828180, 0x87868584, 0x8b8a8988, 0x8f8e8d8c, + 0x93929190, 0x97969594, 0x9b9a9998, 0x9f9e9d9c, + 0x00000000, 0x00000007, 0x43424140, 0x47464544 ] + +property AeadPolyKeyBuildState_correct = + BuildState AeadKey AeadNonce 0 == AeadPolyOneTimeKey_testVector +``` + +After generating Poly1305 one-time key: + +```cryptol +AeadPolyOneTimeKeyState = [ + 0x252bac7b, 0xaf47b42d, 0x557ab609, 0x8455e9a4, + 0x73d6e10a, 0xebd97510, 0x7875932a, 0xff53d53e, + 0xdecc7ea2, 0xb44ddbad, 0xe49c17d1, 0xd8430bc9, + 0x8c94b7bc, 0x8b7d4b4b, 0x3927f67d, 0x1669a432] + +property AeadPolyChaCha_correct = + ChaCha20Block AeadKey AeadNonce 0 == AeadPolyOneTimeKeyState +``` + +Poly1305 Key: + +```cryptol +Poly1305Key_testVector = join (parseHexString ( + "7b ac 2b 25 2d b4 47 af 09 b6 7a 55 a4 e9 55 84 " # + "0a e1 d6 73 10 75 d9 eb 2a 93 75 78 3e d5 53 ff " )) + +property poly1305Test_correct = AeadPolyKey == Poly1305Key_testVector + +Poly1305_r = 0x0455e9a4057ab6080f47b42c052bac7b +Poly1305_s = 0xff53d53e7875932aebd9751073d6e10a +``` + +```verbatim +Keystream bytes: +9f:7b:e9:5d:01:fd:40:ba:15:e2:8f:fb:36:81:0a:ae: +c1:c0:88:3f:09:01:6e:de:dd:8a:d0:87:55:82:03:a5: +4e:9e:cb:38:ac:8e:5e:2b:b8:da:b2:0f:fa:db:52:e8: +75:04:b2:6e:be:69:6d:4f:60:a4:85:cf:11:b8:1b:59: +fc:b1:c4:5f:42:19:ee:ac:ec:6a:de:c3:4e:66:69:78: +8e:db:41:c4:9c:a3:01:e1:27:e0:ac:ab:3b:44:b9:cf: +5c:86:bb:95:e0:6b:0d:f2:90:1a:b6:45:e4:ab:e6:22: +15:38 + + +Ciphertext: +000 d3 1a 8d 34 64 8e 60 db 7b 86 af bc 53 ef 7e c2|...4d.`.{...S.~. +016 a4 ad ed 51 29 6e 08 fe a9 e2 b5 a7 36 ee 62 d6|...Q)n......6.b. +032 3d be a4 5e 8c a9 67 12 82 fa fb 69 da 92 72 8b|=..^..g....i..r. +048 1a 71 de 0a 9e 06 0b 29 05 d6 a5 b6 7e cd 3b 36|.q.....)....~.;6 +064 92 dd bd 7f 2d 77 8b 8c 98 03 ae e3 28 09 1b 58|...-w......(..X +080 fa b3 24 e4 fa d6 75 94 55 85 80 8b 48 31 d7 bc|..$...u.U...H1.. +096 3f f4 de f0 8e 4b 7a 9d e5 76 d2 65 86 ce c6 4b|?....Kz..v.e...K +112 61 16 |a. +``` + +AEAD Construction for Poly1305: + +```cryptol +AeadConstructionTestVector = parseHexString ( + "50:51:52:53:c0:c1:c2:c3:c4:c5:c6:c7:00:00:00:00:" # + "d3:1a:8d:34:64:8e:60:db:7b:86:af:bc:53:ef:7e:c2:" # + "a4:ad:ed:51:29:6e:08:fe:a9:e2:b5:a7:36:ee:62:d6:" # + "3d:be:a4:5e:8c:a9:67:12:82:fa:fb:69:da:92:72:8b:" # + "1a:71:de:0a:9e:06:0b:29:05:d6:a5:b6:7e:cd:3b:36:" # + "92:dd:bd:7f:2d:77:8b:8c:98:03:ae:e3:28:09:1b:58:" # + "fa:b3:24:e4:fa:d6:75:94:55:85:80:8b:48:31:d7:bc:" # + "3f:f4:de:f0:8e:4b:7a:9d:e5:76:d2:65:86:ce:c6:4b:" # + "61:16:00:00:00:00:00:00:00:00:00:00:00:00:00:00:" # + "0c:00:00:00:00:00:00:00:72:00:00:00:00:00:00:00." ) +``` + +Note the 4 zero bytes in line 000 and the 14 zero bytes in line 128 + +```cryptol +// Tag: +AeadTagTestVector = parseHexString "1a:e1:0b:59:4f:09:e2:6a:7e:90:2e:cb:d0:60:06:91." +``` + +```cryptol +property AeadTag_correct = AeadTag == AeadTagTestVector + +property AeadConstruction_correct = (AeadConstruction AeadAAD AeadCT) == AeadConstructionTestVector + +property AeadDecrypt_correct = ptMatches && isValid where + (pt,isValid) = AEAD_CHACHA20_POLY1305_DECRYPT AeadKey (AeadIV # AeadC) cypherText AeadAAD + cypherText = (AEAD_CHACHA20_POLY1305 AeadKey (AeadIV # AeadC) AeadPt AeadAAD) + ptMatches = AeadPt == pt + +``` + +# Implementation Advice + +Each block of ChaCha20 involves 16 move operations and one increment +operation for loading the state, 80 each of XOR, addition and Roll +operations for the rounds, 16 more add operations and 16 XOR +operations for protecting the plaintext. Section 2.3 describes the +ChaCha block function as "adding the original input words". This +implies that before starting the rounds on the ChaCha state, we copy +it aside, only to add it in later. This is correct, but we can save +a few operations if we instead copy the state and do the work on the +copy. This way, for the next block you don't need to recreate the +state, but only to increment the block counter. This saves +approximately 5.5% of the cycles. + +It is not recommended to use a generic big number library such as the +one in OpenSSL for the arithmetic operations in Poly1305. Such +libraries use dynamic allocation to be able to handle any-sized +integer, but that flexibility comes at the expense of performance as +well as side-channel security. More efficient implementations that +run in constant time are available, one of them in DJB's own library, +NaCl ([NaCl]). A constant-time but not optimal approach would be to +naively implement the arithmetic operations for a 288-bit integers, +because even a naive implementation will not exceed 2^288 in the +multiplication of (acc+block) and r. An efficient constant-time +implementation can be found in the public domain library poly1305- +donna ([poly1305_donna]). + + +# Security Considerations + +The ChaCha20 cipher is designed to provide 256-bit security. + +The Poly1305 authenticator is designed to ensure that forged messages +are rejected with a probability of 1-(n/(2^102)) for a 16n-byte +message, even after sending 2^64 legitimate messages, so it is SUF- +CMA in the terminology of [AE]. + +Proving the security of either of these is beyond the scope of this +document. Such proofs are available in the referenced academic +papers. + +The most important security consideration in implementing this draft +is the uniqueness of the nonce used in ChaCha20. Counters and LFSRs +are both acceptable ways of generating unique nonces, as is +encrypting a counter using a 64-bit cipher such as DES. Note that it +is not acceptable to use a truncation of a counter encrypted with a +128-bit or 256-bit cipher, because such a truncation may repeat after +a short time. + +The Poly1305 key MUST be unpredictable to an attacker. Randomly +generating the key would fulfill this requirement, except that +Poly1305 is often used in communications protocols, so the receiver +should know the key. Pseudo-random number generation such as by +encrypting a counter is acceptable. Using ChaCha with a secret key +and a nonce is also acceptable. + +The algorithms presented here were designed to be easy to implement +in constant time to avoid side-channel vulnerabilities. The +operations used in ChaCha20 are all additions, XORs, and fixed +rotations. All of these can and should be implemented in constant +time. Access to offsets into the ChaCha state and the number of +operations do not depend on any property of the key, eliminating the +chance of information about the key leaking through the timing of +cache misses. + +For Poly1305, the operations are addition, multiplication and +modulus, all on >128-bit numbers. This can be done in constant time, +but a naive implementation (such as using some generic big number +library) will not be constant time. For example, if the +multiplication is performed as a separate operation from the modulus, +the result will some times be under 2^256 and some times be above +2^256. Implementers should be careful about timing side-channels for +Poly1305 by using the appropriate implementation of these operations. + + +# IANA Considerations + +There are no IANA considerations for this document. + + +# Acknowledgements + +ChaCha20 and Poly1305 were invented by Daniel J. Bernstein. The AEAD +construction and the method of creating the one-time poly1305 key +were invented by Adam Langley. + +Thanks to Robert Ransom, Watson Ladd, Stefan Buhler, and kenny +patterson for their helpful comments and explanations. Thanks to +Niels Moeller for suggesting the more efficient AEAD construction in +this document. Special thanks to Ilari Liusvaara for providing extra +test vectors, helpful comments, and for being the first to attempt an +implementation from this draft. + +# References + +## Normative References + +```example +[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + +[chacha] Bernstein, D., "ChaCha, a variant of Salsa20", Jan 2008. + +[poly1305] + Bernstein, D., "The Poly1305-AES message-authentication + code", Mar 2005. +``` + +## Informative References + +```example +[AE] Bellare, M. and C. Namprempre, "Authenticated Encryption: + Relations among notions and analysis of the generic + composition paradigm", + . + +[FIPS-197] + National Institute of Standards and Technology, "Advanced + Encryption Standard (AES)", FIPS PUB 197, November 2001. + +[FIPS-46] National Institute of Standards and Technology, "Data + Encryption Standard", FIPS PUB 46-2, December 1993, + . + +[LatinDances] + Aumasson, J., Fischer, S., Khazaei, S., Meier, W., and C. + Rechberger, "New Features of Latin Dances: Analysis of + Salsa, ChaCha, and Rumba", Dec 2007. + +[NaCl] Bernstein, D., Lange, T., and P. Schwabe, "NaCl: + Networking and Cryptography library", + . + +[RFC4868] Kelly, S. and S. Frankel, "Using HMAC-SHA-256, HMAC-SHA- + 384, and HMAC-SHA-512 with IPsec", RFC 4868, May 2007. + +[RFC5116] McGrew, D., "An Interface and Algorithms for Authenticated + Encryption", RFC 5116, January 2008. + +[RFC5996] Kaufman, C., Hoffman, P., Nir, Y., and P. Eronen, + "Internet Key Exchange Protocol Version 2 (IKEv2)", + RFC 5996, September 2010. + +[Zhenqing2012] + Zhenqing, S., Bin, Z., Dengguo, F., and W. Wenling, + "Improved key recovery attacks on reduced-round salsa20 + and chacha", 2012. + +[poly1305_donna] + Floodyberry, A., "Poly1305-donna", + . + +[standby-cipher] + McGrew, D., Grieco, A., and Y. Sheffer, "Selection of + Future Cryptographic Standards", + draft-mcgrew-standby-cipher (work in progress). +``` + + +Authors' Addresses + +```verbatim +Yoav Nir +Check Point Software Technologies Ltd. +5 Hasolelim st. +Tel Aviv 6789735 +Israel +Email: ynir.ietf@gmail.com + +Adam Langley +Google Inc +Email: agl@google.com + +Dylan McNamee +Galois Inc +Email: dylan@galois.com +``` + +# Appendix: Additional test vectors + +## The ChaCha20 Block Functions + +### Test Vector #1 + +```cryptol +TV1Key = zero:ChaChaKey +TV1Nonce = zero:[96] +TV1InitState = BuildState TV1Key TV1Nonce 0 + +TV1After20 = [ + 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, + 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, + 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, + 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2] + +TV1KeyStream = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86] + +property TV1_correct = ChaCha20Block T1Key T1Nonce 0 == TV1After20 + +property TV1Keystream_correct = + take`{0x40} (groupBy`{8} (join (join (ChaCha20ExpandKey TV1Key TV1Nonce 0)))) == + TV1KeyStream + +// TODO: port the rest +``` + +# Appendix: Utility functions + +```cryptol +indexOf e (xs:[a+1]b) = ixs ! 0 where + ixs = [ 0 ] # + [ if ix == e then j else old + | ix <- xs + | j <- [ 0 .. a ] + | old <- ixs + ] + +ToLittleEndian : ChaChaState -> ChaChaState +ToLittleEndian s = [littleendian (split words) | words <- s] + +// Takes a finite sequence of bytes, and turns them into a word via +// a little-endian interpretation +littleendian : {a}(fin a) => [a][8] -> [a*8] +littleendian b = join(reverse b) + +// Converts a bytestring encoded like "fe:ed:fa:ce." into a sequence of bytes +// Note: the trailing punctuation is needed +parseHexString : {n} (fin n) => [3*n][8] -> [n][8] +parseHexString hexString = [ charsToByte (take`{2} cs) | cs <- groupBy`{3} hexString ] where + charsToByte : [2][8] -> [8] + charsToByte [ ub, lb ] = (charToByte ub) << 4 || (charToByte lb) + charToByte c = if c >= '0' && c <= '9' then c-'0' + | c >= 'a' && c <= 'f' then 10+(c-'a') + else 0 // error case + +property parseHexString_check = + join (parseHexString + ("00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:" # + "14:15:16:17:18:19:1a:1b:1c:1d:1e:1f.")) == + 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f + +property AllPropertiesPass = + ChaChaQuarterround_passes_test && + ChaChaQuarterround_passes_column_test && + FirstRow_correct && + BuildState_correct && + ChaChaStateAfter20_correct && + ChaCha20_test1 && + SunscreenBuildState_correct && + SunscreenBuildState2_correct && + SunscreenBlock1_correct && + SunscreenBlock2_correct && + SunscreenKeystream_correct SunscreenKeystream && + ChaCha_encrypt_sunscreen_correct && + Sunscreen_decrypt_correct && + poly1306Sokay && + polyBlocksOK && + Poly1305_passes_test && + PolyBuildState_correct && + PolyChaCha_correct && + Poly_passes_test && + AeadPolyKeyBuildState_correct && + AeadPolyChaCha_correct && + poly1305Test_correct && + AeadTag_correct && + AeadConstruction_correct && + AeadDecrypt_correct && + parseHexString_check + +``` + +Since this file is literate Cryptol, the properties above can be checked +by loading it into a Cryptol interpreter, and running the AllPropertiesPass +function, like this: + +```example +$ cryptol ChaChaPolyCryptolIETF.md + _ _ + ___ _ __ _ _ _ __ | |_ ___ | | + / __| '__| | | | '_ \| __/ _ \| | + | (__| | | |_| | |_) | || (_) | | + \___|_| \__, | .__/ \__\___/|_| + |___/|_| version 2.0.0 (62acc96) + +Loading module Cryptol +Loading module ChaCha20 +... a bunch of warnings about the use of ambiguous-width constants +ChaCha20> AllPropertiesPass +True +``` +This check verifies the implementation of `ChaCha`, `Poly1305` and the `AEAD` +construction all work with the provided test vectors. + + From 00ad314681cdb81d547ea11e6132b51e20836136 Mon Sep 17 00:00:00 2001 From: Joey Dodds Date: Tue, 29 Jul 2014 16:39:50 -0700 Subject: [PATCH 33/45] added most of the test cases --- examples/ChaChaPolyCryptolIETF.md | 515 +++++++++++++++++++++++++++++- 1 file changed, 504 insertions(+), 11 deletions(-) mode change 100644 => 100755 examples/ChaChaPolyCryptolIETF.md diff --git a/examples/ChaChaPolyCryptolIETF.md b/examples/ChaChaPolyCryptolIETF.md old mode 100644 new mode 100755 index e16d8e3b..cddc46fb --- a/examples/ChaChaPolyCryptolIETF.md +++ b/examples/ChaChaPolyCryptolIETF.md @@ -1458,33 +1458,526 @@ Email: dylan@galois.com ## The ChaCha20 Block Functions +```cryptol +property TV_block_correct key nonce blockcounter result = ChaCha20Block key nonce blockcounter == result + +property TV_block_Keystream_correct key nonce blockcounter keystream = + take`{0x40} (groupBy`{8} (join (join (ChaCha20ExpandKey key nonce blockcounter)))) == keystream + +property ChaCha20_block_correct key nonce blockcounter result keystream = + TV_block_correct key nonce blockcounter result && + TV_block_Keystream_correct key nonce blockcounter keystream +``` + ### Test Vector #1 ```cryptol -TV1Key = zero:ChaChaKey -TV1Nonce = zero:[96] -TV1InitState = BuildState TV1Key TV1Nonce 0 +TV1_block_Key = zero:ChaChaKey +TV1_block_Nonce = zero:[96] +TV1_block_BlockCounter = 0 -TV1After20 = [ +TV1_block_After20 = [ 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2] -TV1KeyStream = [ +TV1_block_KeyStream = [ 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86] -property TV1_correct = ChaCha20Block T1Key T1Nonce 0 == TV1After20 +property TV1_block_correct = ChaCha20_block_correct TV1_block_Key TV1_block_Nonce TV1_block_BlockCounter TV1_block_After20 TV1_block_KeyStream -property TV1Keystream_correct = - take`{0x40} (groupBy`{8} (join (join (ChaCha20ExpandKey TV1Key TV1Nonce 0)))) == - TV1KeyStream - -// TODO: port the rest ``` + +### Test Vector #2 + +```cryptol +TV2_block_Key = zero:ChaChaKey +TV2_block_Nonce = zero:[96] +TV2_block_BlockCounter = 1 + +TV2_block_After20 = [ + 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, + 0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32, + 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, + 0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b] + +TV2_block_KeyStream = [ + 0x9f, 0x07, 0xe7, 0xbe, 0x55, 0x51, 0x38, 0x7a, 0x98, 0xba, 0x97, 0x7c, 0x73, 0x2d, 0x08, 0x0d, + 0xcb, 0x0f, 0x29, 0xa0, 0x48, 0xe3, 0x65, 0x69, 0x12, 0xc6, 0x53, 0x3e, 0x32, 0xee, 0x7a, 0xed, + 0x29, 0xb7, 0x21, 0x76, 0x9c, 0xe6, 0x4e, 0x43, 0xd5, 0x71, 0x33, 0xb0, 0x74, 0xd8, 0x39, 0xd5, + 0x31, 0xed, 0x1f, 0x28, 0x51, 0x0a, 0xfb, 0x45, 0xac, 0xe1, 0x0a, 0x1f, 0x4b, 0x79, 0x4d, 0x6f] + +property TV2_block_correct = ChaCha20_block_correct TV2_block_Key TV2_block_Nonce TV2_block_BlockCounter TV2_block_After20 TV2_block_KeyStream + + +``` + +### Test Vector #3 + +```cryptol +TV3_block_Key = (zero # 0b1):ChaChaKey +TV3_block_Nonce = zero:[96] +TV3_block_BlockCounter = 1 + +TV3_block_After20 = [ + 0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, + 0xe8252083, 0x60818b01, 0xf38422b8, 0x5aaa49c9, + 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, + 0x4436274e, 0x2561b3c8, 0xebdd4aa6, 0xa0136c00] + +TV3_block_KeyStream = [ + 0x3a, 0xeb, 0x52, 0x24, 0xec, 0xf8, 0x49, 0x92, 0x9b, 0x9d, 0x82, 0x8d, 0xb1, 0xce, 0xd4, 0xdd, + 0x83, 0x20, 0x25, 0xe8, 0x01, 0x8b, 0x81, 0x60, 0xb8, 0x22, 0x84, 0xf3, 0xc9, 0x49, 0xaa, 0x5a, + 0x8e, 0xca, 0x00, 0xbb, 0xb4, 0xa7, 0x3b, 0xda, 0xd1, 0x92, 0xb5, 0xc4, 0x2f, 0x73, 0xf2, 0xfd, + 0x4e, 0x27, 0x36, 0x44, 0xc8, 0xb3, 0x61, 0x25, 0xa6, 0x4a, 0xdd, 0xeb, 0x00, 0x6c, 0x13, 0xa0] + +property TV3_block_correct = ChaCha20_block_correct TV3_block_Key TV3_block_Nonce TV3_block_BlockCounter TV3_block_After20 TV3_block_KeyStream + +``` + +### Test Vector #4 + +```cryptol +TV4_block_Key = ( 0x00ff # zero):ChaChaKey +TV4_block_Nonce = zero:[96] +TV4_block_BlockCounter = 2 + +TV4_block_After20 = [ + 0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, + 0xa78dea8f, 0x5e269039, 0xa1bebbc1, 0xcaf09aae, + 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, + 0x546ca624, 0x1bec45d5, 0x87f47473, 0x96f0992e] + +TV4_block_KeyStream = [ + 0x72, 0xd5, 0x4d, 0xfb, 0xf1, 0x2e, 0xc4, 0x4b, 0x36, 0x26, 0x92, 0xdf, 0x94, 0x13, 0x7f, 0x32, + 0x8f, 0xea, 0x8d, 0xa7, 0x39, 0x90, 0x26, 0x5e, 0xc1, 0xbb, 0xbe, 0xa1, 0xae, 0x9a, 0xf0, 0xca, + 0x13, 0xb2, 0x5a, 0xa2, 0x6c, 0xb4, 0xa6, 0x48, 0xcb, 0x9b, 0x9d, 0x1b, 0xe6, 0x5b, 0x2c, 0x09, + 0x24, 0xa6, 0x6c, 0x54, 0xd5, 0x45, 0xec, 0x1b, 0x73, 0x74, 0xf4, 0x87, 0x2e, 0x99, 0xf0, 0x96] + +property TV4_block_correct = ChaCha20_block_correct TV4_block_Key TV4_block_Nonce TV4_block_BlockCounter TV4_block_After20 TV4_block_KeyStream + +``` + +### Test Vector #5 + +```cryptol +TV5_block_Key = (zero):ChaChaKey +TV5_block_Nonce = zero # 0x02:[96] +TV5_block_BlockCounter = 0 + +TV5_block_After20 = [ + 0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, + 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, 0xc727ee54, + 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, + 0x99c28f5f, 0x628314e8, 0x398a19fa, 0x6ded1b53] + +TV5_block_KeyStream = [ + 0xc2, 0xc6, 0x4d, 0x37, 0x8c, 0xd5, 0x36, 0x37, 0x4a, 0xe2, 0x04, 0xb9, 0xef, 0x93, 0x3f, 0xcd, + 0x1a, 0x8b, 0x22, 0x88, 0xb3, 0xdf, 0xa4, 0x96, 0x72, 0xab, 0x76, 0x5b, 0x54, 0xee, 0x27, 0xc7, + 0x8a, 0x97, 0x0e, 0x0e, 0x95, 0x5c, 0x14, 0xf3, 0xa8, 0x8e, 0x74, 0x1b, 0x97, 0xc2, 0x86, 0xf7, + 0x5f, 0x8f, 0xc2, 0x99, 0xe8, 0x14, 0x83, 0x62, 0xfa, 0x19, 0x8a, 0x39, 0x53, 0x1b, 0xed, 0x6d] + +property TV5_block_correct = ChaCha20_block_correct TV5_block_Key TV5_block_Nonce TV5_block_BlockCounter TV5_block_After20 TV5_block_KeyStream + +property all_block_tests_correct = + TV1_block_correct && + TV2_block_correct && + TV3_block_correct && + TV4_block_correct && + TV5_block_correct + +``` + +## ChaCha20 Encryption + +```cryptol +property ChaCha20_enc_correct key nonce blockcounter plaintext cyphertext = ChaCha20EncryptBytes plaintext key nonce blockcounter == cyphertext +``` + +### Test Vector #1 + +```cryptol +TV1_enc_Key = (zero):ChaChaKey +TV1_enc_Nonce = zero:[96] +TV1_enc_BlockCounter = 0 + +TV1_enc_plaintext = zero:[64][8] + +TV1_enc_cyphertext = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86] + +property TV1_enc_correct = ChaCha20_enc_correct TV1_enc_Key TV1_enc_Nonce TV1_enc_BlockCounter TV1_enc_plaintext TV1_enc_cyphertext + +``` + +### Test Vector #2 + +```cryptol +TV2_enc_Key = (zero # 0x1):ChaChaKey +TV2_enc_Nonce = zero # 0x2:[96] +TV2_enc_BlockCounter = 1 + +IETF_submission_text = [ + 0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6f, 0x72, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x20, + 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, + 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x22, 0x49, 0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2e, 0x20, 0x53, 0x75, 0x63, 0x68, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, 0x20, + 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c, + 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x72, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f ] + +TV2_enc_plaintext = IETF_submission_text + + +TV2_enc_cyphertext = [ + 0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde, 0x4f, 0x37, 0x6c, 0xa2, 0x3e, 0x82, 0x73, 0x70, + 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f, 0x57, 0xbd, 0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec, + 0x2a, 0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15, 0xc8, 0xf3, 0xd3, 0x37, 0xf7, 0xd3, 0x70, 0x05, + 0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3, 0x9f, 0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d, + 0x40, 0x42, 0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa, 0x4b, 0x4b, 0xb5, 0xe8, 0xea, 0xd0, 0x44, 0x0e, + 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7, 0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50, + 0x42, 0xbd, 0xfa, 0x77, 0x73, 0xd8, 0xa9, 0x05, 0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1, 0x41, 0x1c, + 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05, 0xb7, 0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a, + 0xd0, 0x0f, 0x84, 0x49, 0xed, 0x8f, 0x72, 0xd0, 0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca, 0x66, + 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4, 0x1f, 0x43, 0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d, + 0xc4, 0xb2, 0xd0, 0xdf, 0xb4, 0x8a, 0x6c, 0x91, 0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28, + 0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87, 0x9d, 0x7b, 0x35, 0xd4, 0x9e, 0xb2, 0xe6, 0x2b, + 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89, 0x39, 0xe2, 0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f, + 0xa8, 0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76, 0x59, 0x89, 0xcb, 0xcf, 0x3d, 0xaa, 0x8b, 0x6c, + 0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9, 0x2b, 0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84, + 0xa1, 0xbe, 0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd, 0xa2, 0x36, 0xe7, 0xe8, 0x18, 0xb0, 0x4b, 0x0b, + 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe, 0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0, + 0x8a, 0xaa, 0x9b, 0x63, 0xd1, 0xa1, 0x6f, 0x80, 0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c, 0x41, 0x1f, + 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3, 0x6f, 0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62, + 0xbe, 0xbc, 0xfd, 0x2d, 0xc5, 0xbc, 0xe0, 0x91, 0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6, 0xe6, + 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64, 0x77, 0x33, 0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85, + 0x14, 0xea, 0x99, 0x82, 0xcc, 0xaf, 0xb3, 0x41, 0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab, + 0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba, 0x5b, 0x86, 0x2f, 0x37, 0x30, 0xe3, 0x7c, 0xfd, + 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2, 0x21] + +property TV2_enc_correct = ChaCha20_enc_correct TV2_enc_Key TV2_enc_Nonce TV2_enc_BlockCounter TV2_enc_plaintext TV2_enc_cyphertext + +``` + +### Test Vector #3 + +```cryptol +TV3_enc_Key = join([ + 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, + 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0]):ChaChaKey +TV3_enc_Nonce = zero # 0x2:[96] +TV3_enc_BlockCounter = 42:[32] + +jabberwock_text = [ + 0x27, 0x54, 0x77, 0x61, 0x73, 0x20, 0x62, 0x72, 0x69, 0x6c, 0x6c, 0x69, 0x67, 0x2c, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x6c, 0x69, 0x74, 0x68, 0x79, 0x20, 0x74, 0x6f, + 0x76, 0x65, 0x73, 0x0a, 0x44, 0x69, 0x64, 0x20, 0x67, 0x79, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x67, 0x69, 0x6d, 0x62, 0x6c, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, + 0x61, 0x62, 0x65, 0x3a, 0x0a, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x69, 0x6d, 0x73, 0x79, 0x20, 0x77, + 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6f, 0x72, 0x6f, 0x67, 0x6f, 0x76, 0x65, + 0x73, 0x2c, 0x0a, 0x41, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x6d, 0x65, 0x20, + 0x72, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6f, 0x75, 0x74, 0x67, 0x72, 0x61, 0x62, 0x65, 0x2e] + +TV3_enc_plaintext = jabberwock_text + + +TV3_enc_cyphertext = [ + 0x62, 0xe6, 0x34, 0x7f, 0x95, 0xed, 0x87, 0xa4, 0x5f, 0xfa, 0xe7, 0x42, 0x6f, 0x27, 0xa1, 0xdf, + 0x5f, 0xb6, 0x91, 0x10, 0x04, 0x4c, 0x0d, 0x73, 0x11, 0x8e, 0xff, 0xa9, 0x5b, 0x01, 0xe5, 0xcf, + 0x16, 0x6d, 0x3d, 0xf2, 0xd7, 0x21, 0xca, 0xf9, 0xb2, 0x1e, 0x5f, 0xb1, 0x4c, 0x61, 0x68, 0x71, + 0xfd, 0x84, 0xc5, 0x4f, 0x9d, 0x65, 0xb2, 0x83, 0x19, 0x6c, 0x7f, 0xe4, 0xf6, 0x05, 0x53, 0xeb, + 0xf3, 0x9c, 0x64, 0x02, 0xc4, 0x22, 0x34, 0xe3, 0x2a, 0x35, 0x6b, 0x3e, 0x76, 0x43, 0x12, 0xa6, + 0x1a, 0x55, 0x32, 0x05, 0x57, 0x16, 0xea, 0xd6, 0x96, 0x25, 0x68, 0xf8, 0x7d, 0x3f, 0x3f, 0x77, + 0x04, 0xc6, 0xa8, 0xd1, 0xbc, 0xd1, 0xbf, 0x4d, 0x50, 0xd6, 0x15, 0x4b, 0x6d, 0xa7, 0x31, 0xb1, + 0x87, 0xb5, 0x8d, 0xfd, 0x72, 0x8a, 0xfa, 0x36, 0x75, 0x7a, 0x79, 0x7a, 0xc1, 0x88, 0xd1] + +property TV3_enc_correct = ChaCha20_enc_correct TV3_enc_Key TV3_enc_Nonce TV3_enc_BlockCounter TV3_enc_plaintext TV3_enc_cyphertext + +property all_enc_tests_correct = + TV1_enc_correct && + TV2_enc_correct && + TV3_enc_correct +``` + +## Poly1305 Message Authentication Code + +```cryptol +property poly1305_MAC_correct key text tag = Poly1305 key text == tag +``` + +### Test Vector #1 + +```cryptol +TV1_MAC_Key = zero:[256] + +TV1_MAC_text = zero:[64][8] + +TV1_MAC_tag = zero : [16][8] + +property TV1_MAC_correct = poly1305_MAC_correct TV1_MAC_Key TV1_MAC_text TV1_MAC_tag +``` + +### Test Vector #2 + +```cryptol +reused_key = [0x36, 0xe5, 0xf6, 0xb5, 0xc5, 0xe0, 0x60, 0x70, 0xf0, 0xef, 0xca, 0x96, 0x22, 0x7a, 0x86, 0x3e] +TV2_MAC_Key = zero # join(reused_key):[256] + +TV2_MAC_text = IETF_submission_text + +TV2_MAC_tag = reused_key: [16][8] + +property TV2_MAC_correct = poly1305_MAC_correct TV2_MAC_Key TV2_MAC_text TV2_MAC_tag +``` + +### Test Vector #3 + +```cryptol +TV3_MAC_Key = join(reused_key) # 0:[256] + +TV3_MAC_text = IETF_submission_text + +TV3_MAC_tag = [0xf3, 0x47, 0x7e, 0x7c, 0xd9, 0x54, 0x17, 0xaf, 0x89, 0xa6, 0xb8, 0x79, 0x4c, 0x31, 0x0c, 0xf0]: [16][8] + +property TV3_MAC_correct = poly1305_MAC_correct TV3_MAC_Key TV3_MAC_text TV3_MAC_tag +``` + +### Test Vector #4 + +```cryptol +TV4_MAC_Key = join( + [0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, + 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0]):[256] + +TV4_MAC_text = jabberwock_text + +TV4_MAC_tag = [0x45, 0x41, 0x66, 0x9a, 0x7e, 0xaa, 0xee, 0x61, 0xe7, 0x08, 0xdc, 0x7c, 0xbc, 0xc5, 0xeb, 0x62]: [16][8] + +property TV4_MAC_correct = poly1305_MAC_correct TV4_MAC_Key TV4_MAC_text TV4_MAC_tag +``` + +### Test Vector #5 + +If one uses 130-bit partial reduction, does the code +handle the case where partially reduced final result is not fully +reduced? + +```cryptol + +TV5_MAC_Key = 0x02 # zero:[256] + +FF_16 = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] +TV5_MAC_text = FF_16 + +TV5_MAC_tag = split(0x03 # zero): [16][8] + +property TV5_MAC_correct = poly1305_MAC_correct TV5_MAC_Key TV5_MAC_text TV5_MAC_tag +``` + +### Test Vector #6 + +What happens if addition of s overflows modulo 2^128? + +```cryptol +TV6_MAC_Key = 0x02 # zero # join(FF_16):[256] + +TV6_MAC_text = split(0x02 # zero) : [16][8] + +TV6_MAC_tag = split(0x03 # 0): [16][8] + +property TV6_MAC_correct = poly1305_MAC_correct TV6_MAC_Key TV6_MAC_text TV6_MAC_tag +``` + +### Test Vector #7 + +What happens if data limb is all ones and there is +carry from lower limb? + +```cryptol + +TV7_MAC_Key = 0x01 # zero:[256] + +TV7_MAC_text = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + +TV7_MAC_tag = split(0x05 # zero): [16][8] + +property TV7_MAC_correct = poly1305_MAC_correct TV7_MAC_Key TV7_MAC_text TV7_MAC_tag +``` + +### Test Vector #8 + +What happens if final result from polynomial part is +exactly 2^130-5? + +```cryptol + +TV8_MAC_Key = 0x01 # zero:[256] + +TV8_MAC_text = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01] + +TV8_MAC_tag = split(zero): [16][8] + +property TV8_MAC_correct = poly1305_MAC_correct TV8_MAC_Key TV8_MAC_text TV8_MAC_tag +``` + +### Test Vector #9 + +What happens if final result from polynomial part is +exactly 2^130-6? + +```cryptol + +TV9_MAC_Key = 0x02 # zero:[256] + +TV9_MAC_text = + [0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] + +TV9_MAC_tag = [0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]: [16][8] + +property TV9_MAC_correct = poly1305_MAC_correct TV9_MAC_Key TV9_MAC_text TV9_MAC_tag +``` + +### Test Vector #10 + +What happens if 5*H+L-type reduction produces 131- +bit intermediate result? + +```cryptol + +TV10_MAC_Key = join([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) # 0 :[256] + +TV10_MAC_text = [ + 0xE3, 0x35, 0x94, 0xD7, 0x50, 0x5E, 0x43, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x94, 0xD7, 0x50, 0x5E, 0x43, 0x79, 0xCD, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + +TV10_MAC_tag = [0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]: [16][8] + +property TV10_MAC_correct = poly1305_MAC_correct TV10_MAC_Key TV10_MAC_text TV10_MAC_tag +``` + +### Test Vector #11 + +What happens if 5*H+L-type reduction produces 131- +bit final result? + +```cryptol + +TV11_MAC_Key = join([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) # 0 :[256] + +TV11_MAC_text = [ + 0xE3, 0x35, 0x94, 0xD7, 0x50, 0x5E, 0x43, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x94, 0xD7, 0x50, 0x5E, 0x43, 0x79, 0xCD, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + +TV11_MAC_tag = split(0x13 # 0): [16][8] + +property TV11_MAC_correct = poly1305_MAC_correct TV11_MAC_Key TV11_MAC_text TV11_MAC_tag + +property all_MAC_tests_correct = + TV1_MAC_correct && + TV2_MAC_correct && + TV3_MAC_correct && + TV4_MAC_correct && + TV5_MAC_correct && + TV6_MAC_correct && + TV7_MAC_correct && + TV8_MAC_correct && + TV9_MAC_correct && + TV10_MAC_correct && + TV11_MAC_correct + +``` + +## Poly1305 Key Generation Using ChaCha20 + +```cryptol +Poly1305_key_correct key nonce otk = GeneratePolyKeyUsingChaCha key nonce 0 == otk +``` + +### Test Vector #1 + +```cryptol +TV1_key_Key = zero:ChaChaKey +TV1_key_Nonce = zero:[96] + +TV1_key_OneTimeKey = join([ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7]) + +property TV1_key_correct = Poly1305_key_correct TV1_key_Key TV1_key_Nonce TV1_key_OneTimeKey +``` + +### Test Vector #2 + +```cryptol +TV2_key_Key = zero # 0x01:ChaChaKey +TV2_key_Nonce = zero # 0x02:[96] + +TV2_key_OneTimeKey = join([ + 0xec, 0xfa, 0x25, 0x4f, 0x84, 0x5f, 0x64, 0x74, 0x73, 0xd3, 0xcb, 0x14, 0x0d, 0xa9, 0xe8, 0x76, + 0x06, 0xcb, 0x33, 0x06, 0x6c, 0x44, 0x7b, 0x87, 0xbc, 0x26, 0x66, 0xdd, 0xe3, 0xfb, 0xb7, 0x39]) + +property TV2_key_correct = Poly1305_key_correct TV2_key_Key TV2_key_Nonce TV2_key_OneTimeKey +``` + +### Test Vector #3 + +```cryptol +TV3_key_Key = join([ + 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, + 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0]):ChaChaKey +TV3_key_Nonce = zero # 0x02:[96] + +TV3_key_OneTimeKey = join([ + 0x96, 0x5e, 0x3b, 0xc6, 0xf9, 0xec, 0x7e, 0xd9, 0x56, 0x08, 0x08, 0xf4, 0xd2, 0x29, 0xf9, 0x4b, + 0x13, 0x7f, 0xf2, 0x75, 0xca, 0x9b, 0x3f, 0xcb, 0xdd, 0x59, 0xde, 0xaa, 0xd2, 0x33, 0x10, 0xae]) + +property TV3_key_correct = Poly1305_key_correct TV3_key_Key TV3_key_Nonce TV3_key_OneTimeKey + +property all_key_tests_correct = + TV1_key_correct && + TV2_key_correct && + TV3_key_correct + +property all_tests_correct = + all_block_tests_correct && + all_enc_tests_correct && + all_MAC_tests_correct && + all_key_tests_correct +``` + # Appendix: Utility functions From 67a28f4e73436103ea53fd5c50cd3af3225a3eae Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 30 Jul 2014 17:08:24 -0700 Subject: [PATCH 34/45] Change the panic message Switch from requesting bug reports to "cryptol-bugs@galois.com" to the github issue tracker. --- src/Cryptol/Utils/Panic.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cryptol/Utils/Panic.hs b/src/Cryptol/Utils/Panic.hs index 03cb780e..8ae08de5 100644 --- a/src/Cryptol/Utils/Panic.hs +++ b/src/Cryptol/Utils/Panic.hs @@ -26,7 +26,7 @@ data CryptolPanic = CryptolPanic { panicLoc :: String instance Show CryptolPanic where show p = unlines $ [ "You have encountered a bug in Cryptol's implementation." - , "*** Please report this problem to cryptol-bugs@galois.com" + , "*** Please create an issue at https://github.com/galoisinc/cryptol/issues" , "" , "%< --------------------------------------------------- " ] ++ rev ++ From 8a37dac522fd38e56a90c59cd941dafa4528e79e Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Thu, 31 Jul 2014 16:37:30 -0700 Subject: [PATCH 35/45] Add "boolector" and "mathsat" as possible prover settings. Support for these provers was added in SBV version 3. --- cryptol/REPL/Monad.hs | 12 ++++++++---- src/Cryptol/Symbolic.hs | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/cryptol/REPL/Monad.hs b/cryptol/REPL/Monad.hs index a672c1f5..63817f1d 100644 --- a/cryptol/REPL/Monad.hs +++ b/cryptol/REPL/Monad.hs @@ -56,6 +56,7 @@ import qualified Cryptol.TypeCheck.AST as T import Cryptol.Utils.PP import Cryptol.Utils.Panic (panic) import qualified Cryptol.Parser.AST as P +import Cryptol.Symbolic (proverNames) import Control.Monad (unless,when) import Data.IORef @@ -382,8 +383,8 @@ userOptions = mkOptionMap "The number of elements to display for infinite sequences." , OptionDescr "tests" (EnvNum 100) (const Nothing) "The number of random tests to try." - , OptionDescr "prover" (EnvString "cvc4") checkProver - "The external smt solver for :prove and :sat (cvc4, yices, or z3)." + , OptionDescr "prover" (EnvString "cvc4") checkProver $ + "The external smt solver for :prove and :sat (" ++ proverListString ++ ")." , OptionDescr "iteSolver" (EnvBool False) (const Nothing) "Use smt solver to filter conditional branches in proofs." , OptionDescr "warnDefaulting" (EnvBool True) (const Nothing) @@ -408,10 +409,13 @@ checkInfLength val = case val of checkProver :: EnvVal -> Maybe String checkProver val = case val of EnvString s - | s `elem` ["cvc4", "yices", "z3"] -> Nothing - | otherwise -> Just "prover must be cvc4, yices, or z3" + | s `elem` proverNames -> Nothing + | otherwise -> Just $ "prover must be " ++ proverListString _ -> Just "unable to parse a value for prover" +proverListString :: String +proverListString = concatMap (++ ", ") (init proverNames) ++ "or " ++ last proverNames + -- Environment Utilities ------------------------------------------------------- whenDebug :: REPL () -> REPL () diff --git a/src/Cryptol/Symbolic.hs b/src/Cryptol/Symbolic.hs index 63ab1ef7..6099fb49 100644 --- a/src/Cryptol/Symbolic.hs +++ b/src/Cryptol/Symbolic.hs @@ -40,11 +40,23 @@ import Cryptol.Utils.Panic(panic) -- External interface ---------------------------------------------------------- +proverConfigs :: [(String, SBV.SMTConfig)] +proverConfigs = + [ ("cvc4" , SBV.cvc4 ) + , ("yices" , SBV.yices ) + , ("z3" , SBV.z3 ) + , ("boolector", SBV.boolector) + , ("mathsat" , SBV.mathSAT ) + ] + +proverNames :: [String] +proverNames = map fst proverConfigs + lookupProver :: String -> SBV.SMTConfig -lookupProver "cvc4" = SBV.cvc4 -lookupProver "yices" = SBV.yices -lookupProver "z3" = SBV.z3 -lookupProver s = error $ "invalid prover: " ++ s +lookupProver s = + case lookup s proverConfigs of + Just cfg -> cfg + Nothing -> error $ "invalid prover: " ++ s prove :: (String, Bool, Bool) -> (Expr, Schema) -> M.ModuleCmd (Either String (Maybe [Eval.Value])) prove (proverName, useSolverIte, verbose) (expr, schema) = protectStack useSolverIte $ \modEnv -> do From 72c870cb129825169939a70f15da0edd53f1d54b Mon Sep 17 00:00:00 2001 From: Joey Dodds Date: Fri, 1 Aug 2014 09:15:58 -0700 Subject: [PATCH 36/45] small fixes for push --- examples/ChaChaPolyCryptolIETF.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/examples/ChaChaPolyCryptolIETF.md b/examples/ChaChaPolyCryptolIETF.md index cddc46fb..392e9c7b 100755 --- a/examples/ChaChaPolyCryptolIETF.md +++ b/examples/ChaChaPolyCryptolIETF.md @@ -1970,7 +1970,26 @@ property all_key_tests_correct = TV1_key_correct && TV2_key_correct && TV3_key_correct +``` + +## ChaCha20-Poly1305 AEAD Decryption + +Below we’ll see decrypting a message. We receive a ciphertext, a +nonce, and a tag. We know the key. We will check the tag, and then +(assuming that it validates) decrypt the ciphertext. In this +particular protocol, we’ll assume that there is no padding of the +plaintext. + +```cryptol +AEAD_correct key nonce cypherText AAD tag = ptMatches && isValid where + (pt,isValid) = AEAD_CHACHA20_POLY1305_DECRYPT key nonce cypherText AAD + cypherText = (AEAD_CHACHA20_POLY1305 key nonce AeadPt AAD) + ptMatches = tag == pt +``` + + +```cryptol property all_tests_correct = all_block_tests_correct && all_enc_tests_correct && From d248b50b4065877aa16a050089f3dbcdc152760a Mon Sep 17 00:00:00 2001 From: Joey Dodds Date: Mon, 4 Aug 2014 10:52:50 -0700 Subject: [PATCH 37/45] finished test vectors created function AeadConstruction that is used to generate tags in AEAD encryption and decryption --- examples/ChaChaPolyCryptolIETF.md | 154 +++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 22 deletions(-) diff --git a/examples/ChaChaPolyCryptolIETF.md b/examples/ChaChaPolyCryptolIETF.md index 392e9c7b..d5f9ca9f 100755 --- a/examples/ChaChaPolyCryptolIETF.md +++ b/examples/ChaChaPolyCryptolIETF.md @@ -1073,10 +1073,17 @@ takes a 256-bit key and 96-bit nonce as follows: adlen : [8][8] adlen = groupBy`{8}(littleendian (groupBy`{8}(`n:[64]))) // compute padding - type p1 = (16-m%16)%16 - type p2 = (16-n%16)%16 - tag : [16][8] - tag = Poly1305 PolyKey (aad # (zero:[p1][8]) # ct # (zero:[p2][8]) # adlen # ptlen) + tag = Poly1305 PolyKey (AeadConstruction aad ct) + +//ct in this function has tag removed +AeadConstruction (AAD : [n][8]) (CT : [m][8]) = (AAD # padding1 # CT # padding2 # adlen # ptlen) where + padding1 = (zero:[(16-n%16)][8]) + padding2 = (zero:[(16-m%16)][8]) + adlen : [8][8] + adlen = groupBy`{8}(littleendian (groupBy`{8}(`n:[64]))) + ptlen : [8][8] + ptlen = groupBy`{8}(littleendian (groupBy`{8}(`m:[64]))) + ``` The output from the AEAD is twofold: @@ -1087,13 +1094,11 @@ The output from the AEAD is twofold: Decryption is pretty much the same thing. ```cryptol - AEAD_CHACHA20_POLY1305_DECRYPT : {m, n} (fin m, fin n ,64 >= width m, 64 >= width n) => [256] -> [96] -> [m+16][8] -> [n][8] - -> ([m][8], Bit) - + -> ([m][8], Bit) AEAD_CHACHA20_POLY1305_DECRYPT k nonce ct ad = (pt, valid) where inTag = drop`{m}ct inCt = take`{m}ct @@ -1103,10 +1108,7 @@ AEAD_CHACHA20_POLY1305_DECRYPT k nonce ct ad = (pt, valid) where ptlen = groupBy`{8}(littleendian (groupBy`{8}(`m:[64]))) adlen : [8][8] adlen = groupBy`{8}(littleendian (groupBy`{8}(`n:[64]))) - // compute padding - type p1 = (16-m%16)%16 - type p2 = (16-n%16)%16 - tag = Poly1305 PolyKey (ad # (zero:[p1][8]) # inCt # (zero:[p2][8]) # adlen # ptlen) + tag = Poly1305 PolyKey (AeadConstruction ad inCt) valid = tag == inTag ``` @@ -1161,13 +1163,6 @@ AeadNonce = AeadC # AeadIV ```cryptol AeadCT = ChaCha20EncryptBytes AeadPt AeadKey AeadNonce 1 -type p1width = 4 -type p2width = 14 - -AeadConstruction AAD CT = (AeadAAD # padding1 # AeadCT # padding2 # ADleLen # CTleLen) where - padding1 = zero:[p1width][8] - padding2 = zero:[p2width][8] - AeadPolyKey = GeneratePolyKeyUsingChaCha AeadKey (AeadC # AeadIV) 0 ADleLen : [8][8] @@ -1981,20 +1976,134 @@ particular protocol, we’ll assume that there is no padding of the plaintext. ```cryptol -AEAD_correct key nonce cypherText AAD tag = ptMatches && isValid where +AEAD_correct key nonce cypherText tag AAD = ptMatches && isValid where (pt,isValid) = AEAD_CHACHA20_POLY1305_DECRYPT key nonce cypherText AAD cypherText = (AEAD_CHACHA20_POLY1305 key nonce AeadPt AAD) ptMatches = tag == pt ``` +```cryptol +//known +TV1_AEAD_key = join([ + 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, + 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0]) +//sent +TV1_AEAD_nonce = join([0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]) + +//sent +TV1_AEAD_AAD = [0xf3, 0x33, 0x88, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x91] + +// calculated +TV1_AEAD_known_otk = join([ + 0xbd, 0xf0, 0x4a, 0xa9, 0x5c, 0xe4, 0xde, 0x89, 0x95, 0xb1, 0x4b, 0xb6, 0xa1, 0x8f, 0xec, 0xaf, + 0x26, 0x47, 0x8f, 0x50, 0xc0, 0x54, 0xf5, 0x63, 0xdb, 0xc0, 0xa2, 0x1e, 0x26, 0x15, 0x72, 0xaa]) + +//sent +TV1_AEAD_tag = [0xee, 0xad, 0x9d, 0x67, 0x89, 0x0c, 0xbb, 0x22, 0x39, 0x23, 0x36, 0xfe, 0xa1, 0x85, 0x1f, 0x38] + +TV1_AEAD_cypherText = [ + 0x64, 0xa0, 0x86, 0x15, 0x75, 0x86, 0x1a, 0xf4, 0x60, 0xf0, 0x62, 0xc7, 0x9b, 0xe6, 0x43, 0xbd, + 0x5e, 0x80, 0x5c, 0xfd, 0x34, 0x5c, 0xf3, 0x89, 0xf1, 0x08, 0x67, 0x0a, 0xc7, 0x6c, 0x8c, 0xb2, + 0x4c, 0x6c, 0xfc, 0x18, 0x75, 0x5d, 0x43, 0xee, 0xa0, 0x9e, 0xe9, 0x4e, 0x38, 0x2d, 0x26, 0xb0, + 0xbd, 0xb7, 0xb7, 0x3c, 0x32, 0x1b, 0x01, 0x00, 0xd4, 0xf0, 0x3b, 0x7f, 0x35, 0x58, 0x94, 0xcf, + 0x33, 0x2f, 0x83, 0x0e, 0x71, 0x0b, 0x97, 0xce, 0x98, 0xc8, 0xa8, 0x4a, 0xbd, 0x0b, 0x94, 0x81, + 0x14, 0xad, 0x17, 0x6e, 0x00, 0x8d, 0x33, 0xbd, 0x60, 0xf9, 0x82, 0xb1, 0xff, 0x37, 0xc8, 0x55, + 0x97, 0x97, 0xa0, 0x6e, 0xf4, 0xf0, 0xef, 0x61, 0xc1, 0x86, 0x32, 0x4e, 0x2b, 0x35, 0x06, 0x38, + 0x36, 0x06, 0x90, 0x7b, 0x6a, 0x7c, 0x02, 0xb0, 0xf9, 0xf6, 0x15, 0x7b, 0x53, 0xc8, 0x67, 0xe4, + 0xb9, 0x16, 0x6c, 0x76, 0x7b, 0x80, 0x4d, 0x46, 0xa5, 0x9b, 0x52, 0x16, 0xcd, 0xe7, 0xa4, 0xe9, + 0x90, 0x40, 0xc5, 0xa4, 0x04, 0x33, 0x22, 0x5e, 0xe2, 0x82, 0xa1, 0xb0, 0xa0, 0x6c, 0x52, 0x3e, + 0xaf, 0x45, 0x34, 0xd7, 0xf8, 0x3f, 0xa1, 0x15, 0x5b, 0x00, 0x47, 0x71, 0x8c, 0xbc, 0x54, 0x6a, + 0x0d, 0x07, 0x2b, 0x04, 0xb3, 0x56, 0x4e, 0xea, 0x1b, 0x42, 0x22, 0x73, 0xf5, 0x48, 0x27, 0x1a, + 0x0b, 0xb2, 0x31, 0x60, 0x53, 0xfa, 0x76, 0x99, 0x19, 0x55, 0xeb, 0xd6, 0x31, 0x59, 0x43, 0x4e, + 0xce, 0xbb, 0x4e, 0x46, 0x6d, 0xae, 0x5a, 0x10, 0x73, 0xa6, 0x72, 0x76, 0x27, 0x09, 0x7a, 0x10, + 0x49, 0xe6, 0x17, 0xd9, 0x1d, 0x36, 0x10, 0x94, 0xfa, 0x68, 0xf0, 0xff, 0x77, 0x98, 0x71, 0x30, + 0x30, 0x5b, 0xea, 0xba, 0x2e, 0xda, 0x04, 0xdf, 0x99, 0x7b, 0x71, 0x4d, 0x6c, 0x6f, 0x2c, 0x29, + 0xa6, 0xad, 0x5c, 0xb4, 0x02, 0x2b, 0x02, 0x70, 0x9b] + +TV1_AEAD_Poly_input = [ + 0xf3, 0x33, 0x88, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x91, 0x00, 0x00, 0x00, 0x00, + 0x64, 0xa0, 0x86, 0x15, 0x75, 0x86, 0x1a, 0xf4, 0x60, 0xf0, 0x62, 0xc7, 0x9b, 0xe6, 0x43, 0xbd, + 0x5e, 0x80, 0x5c, 0xfd, 0x34, 0x5c, 0xf3, 0x89, 0xf1, 0x08, 0x67, 0x0a, 0xc7, 0x6c, 0x8c, 0xb2, + 0x4c, 0x6c, 0xfc, 0x18, 0x75, 0x5d, 0x43, 0xee, 0xa0, 0x9e, 0xe9, 0x4e, 0x38, 0x2d, 0x26, 0xb0, + 0xbd, 0xb7, 0xb7, 0x3c, 0x32, 0x1b, 0x01, 0x00, 0xd4, 0xf0, 0x3b, 0x7f, 0x35, 0x58, 0x94, 0xcf, + 0x33, 0x2f, 0x83, 0x0e, 0x71, 0x0b, 0x97, 0xce, 0x98, 0xc8, 0xa8, 0x4a, 0xbd, 0x0b, 0x94, 0x81, + 0x14, 0xad, 0x17, 0x6e, 0x00, 0x8d, 0x33, 0xbd, 0x60, 0xf9, 0x82, 0xb1, 0xff, 0x37, 0xc8, 0x55, + 0x97, 0x97, 0xa0, 0x6e, 0xf4, 0xf0, 0xef, 0x61, 0xc1, 0x86, 0x32, 0x4e, 0x2b, 0x35, 0x06, 0x38, + 0x36, 0x06, 0x90, 0x7b, 0x6a, 0x7c, 0x02, 0xb0, 0xf9, 0xf6, 0x15, 0x7b, 0x53, 0xc8, 0x67, 0xe4, + 0xb9, 0x16, 0x6c, 0x76, 0x7b, 0x80, 0x4d, 0x46, 0xa5, 0x9b, 0x52, 0x16, 0xcd, 0xe7, 0xa4, 0xe9, + 0x90, 0x40, 0xc5, 0xa4, 0x04, 0x33, 0x22, 0x5e, 0xe2, 0x82, 0xa1, 0xb0, 0xa0, 0x6c, 0x52, 0x3e, + 0xaf, 0x45, 0x34, 0xd7, 0xf8, 0x3f, 0xa1, 0x15, 0x5b, 0x00, 0x47, 0x71, 0x8c, 0xbc, 0x54, 0x6a, + 0x0d, 0x07, 0x2b, 0x04, 0xb3, 0x56, 0x4e, 0xea, 0x1b, 0x42, 0x22, 0x73, 0xf5, 0x48, 0x27, 0x1a, + 0x0b, 0xb2, 0x31, 0x60, 0x53, 0xfa, 0x76, 0x99, 0x19, 0x55, 0xeb, 0xd6, 0x31, 0x59, 0x43, 0x4e, + 0xce, 0xbb, 0x4e, 0x46, 0x6d, 0xae, 0x5a, 0x10, 0x73, 0xa6, 0x72, 0x76, 0x27, 0x09, 0x7a, 0x10, + 0x49, 0xe6, 0x17, 0xd9, 0x1d, 0x36, 0x10, 0x94, 0xfa, 0x68, 0xf0, 0xff, 0x77, 0x98, 0x71, 0x30, + 0x30, 0x5b, 0xea, 0xba, 0x2e, 0xda, 0x04, 0xdf, 0x99, 0x7b, 0x71, 0x4d, 0x6c, 0x6f, 0x2c, 0x29, + 0xa6, 0xad, 0x5c, 0xb4, 0x02, 0x2b, 0x02, 0x70, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +``` + +First, we calculate the one-time Poly1305 key + +```cryptol + +//generate and check the one time key (leaving out the given states from the document, they will be correct if this is correct) +property TV1_otk_correct = Poly1305_key_correct TV1_AEAD_key TV1_AEAD_nonce TV1_AEAD_known_otk + +``` + +Next, we construct the AEAD buffer + +```cryptol +property poly_input_correct AeadAAD cypherText result = (AeadConstruction AeadAAD cypherText) == result + +property TV1_poly_input_correct = (poly_input_correct TV1_AEAD_AAD TV1_AEAD_cypherText TV1_AEAD_Poly_input) +``` + +We calculate the Poly1305 tag and find that it matches + +```cryptol +property TV1_tag_correct = poly1305_MAC_correct TV1_AEAD_known_otk (AeadConstruction TV1_AEAD_AAD TV1_AEAD_cypherText) TV1_AEAD_tag +``` ```cryptol -property all_tests_correct = +TV1_plaintext = [ + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x64, 0x72, 0x61, 0x66, 0x74, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, + 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x69, 0x78, 0x20, 0x6d, + 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65, + 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x64, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x6f, 0x62, 0x73, 0x6f, 0x6c, 0x65, 0x74, 0x65, 0x64, + 0x20, 0x62, 0x79, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x70, 0x72, + 0x69, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x20, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x69, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, + 0x6d, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x61, 0x73, 0x20, + 0x2f, 0xe2, 0x80, 0x9c, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x2e, 0x2f, 0xe2, 0x80, 0x9d] + + +TV1_calculate_plaintext = AEAD_CHACHA20_POLY1305_DECRYPT TV1_AEAD_key TV1_AEAD_nonce (TV1_AEAD_cypherText # TV1_AEAD_tag) TV1_AEAD_AAD + +property TV1_plaintext_correct = isValid && pt == TV1_plaintext where + (pt,isValid) = TV1_calculate_plaintext + +property decryption_vector_correct = + TV1_plaintext_correct && + TV1_tag_correct && + TV1_otk_correct + + +property all_test_vectors_correct = all_block_tests_correct && all_enc_tests_correct && all_MAC_tests_correct && - all_key_tests_correct + all_key_tests_correct && + decryption_vector_correct ``` @@ -2059,7 +2168,8 @@ property AllPropertiesPass = AeadTag_correct && AeadConstruction_correct && AeadDecrypt_correct && - parseHexString_check + parseHexString_check && + all_test_vectors_correct ``` From 739adffb67ddd53a88e22414d6f4d5c81588c663 Mon Sep 17 00:00:00 2001 From: Sam Anklesaria Date: Mon, 4 Aug 2014 23:39:47 -0700 Subject: [PATCH 38/45] added SignCast instance and exported sbv --- cryptol.cabal | 8 +-- src/Cryptol/Symbolic/BitVector.hs | 90 +++++++++++++++++++------------ 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/cryptol.cabal b/cryptol.cabal index 91ec2a39..ac8d82eb 100644 --- a/cryptol.cabal +++ b/cryptol.cabal @@ -114,9 +114,6 @@ library Cryptol.Symbolic.Prims Cryptol.Symbolic.Value - - Other-modules: Cryptol.Parser.LexerUtils, - Cryptol.Parser.ParserUtils, Data.SBV, Data.SBV.Bridge.Boolector, Data.SBV.Bridge.CVC4, @@ -124,6 +121,10 @@ library Data.SBV.Bridge.Yices, Data.SBV.Bridge.Z3, Data.SBV.Internals, + Data.SBV.Tools.Polynomial + + Other-modules: Cryptol.Parser.LexerUtils, + Cryptol.Parser.ParserUtils, Data.SBV.BitVectors.AlgReals, Data.SBV.BitVectors.Data, Data.SBV.BitVectors.Model, @@ -147,7 +148,6 @@ library Data.SBV.Tools.ExpectedValue, Data.SBV.Tools.GenTest, Data.SBV.Tools.Optimize, - Data.SBV.Tools.Polynomial, Data.SBV.Utils.Boolean, Data.SBV.Utils.TDiff, Data.SBV.Utils.Lib, diff --git a/src/Cryptol/Symbolic/BitVector.hs b/src/Cryptol/Symbolic/BitVector.hs index 5b007749..0d49f21b 100644 --- a/src/Cryptol/Symbolic/BitVector.hs +++ b/src/Cryptol/Symbolic/BitVector.hs @@ -8,6 +8,8 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} module Cryptol.Symbolic.BitVector where @@ -21,7 +23,7 @@ import Data.SBV.BitVectors.Data -- BitVector type -------------------------------------------------------------- -data BitVector = BV { width :: !Int, val :: !Integer } +data BitVector = BV { signedcxt :: Bool, width :: !Int, val :: !Integer } deriving (Eq, Ord, Show) -- ^ Invariant: BV w x requires that 0 <= w and 0 <= x < 2^w. @@ -30,44 +32,64 @@ bitMask w = bit w - 1 -- | Smart constructor for bitvectors. bv :: Int -> Integer -> BitVector -bv w x = BV w (x .&. bitMask w) +bv = sbv False -unsigned :: BitVector -> Integer -unsigned = val +sbv :: Bool -> Int -> Integer -> BitVector +sbv b w x = BV b w (x .&. bitMask w) -signed :: BitVector -> Integer -signed (BV w x) +unsigned :: Int -> Integer -> Integer +unsigned w x = x + bit w + +signed :: Int -> Integer -> Integer +signed w x | w > 0 && testBit x (w - 1) = x - bit w | otherwise = x same :: Int -> Int -> Int same m n = if m == n then m else error $ "BitVector size mismatch: " ++ show (m, n) +instance SignCast SWord SWord where + signCast (SBV (KBounded _ w) (Left (cwVal -> (CWInteger x)))) = + SBV k (Left (CW k (CWInteger (signed w x)))) where + k = KBounded True w + signCast x@(SBV (KBounded _ w) _) = SBV k (Right (cache y)) where + k = KBounded True w + y st = do xsw <- sbvToSW st x + newExpr st k (SBVApp (Extract (intSizeOf x-1) 0) [xsw]) + signCast _ = error "SignCast not called on BitVector value" + unsignCast (SBV (KBounded _ w) (Left (cwVal -> (CWInteger x)))) = + SBV k (Left (CW k (CWInteger (unsigned w x)))) where + k = KBounded False w + unsignCast x@(SBV (KBounded _ w) _) = SBV k (Right (cache y)) where + k = KBounded False w + y st = do xsw <- sbvToSW st x + newExpr st k (SBVApp (Extract (intSizeOf x-1) 0) [xsw]) + instance Num BitVector where fromInteger n = error $ "fromInteger " ++ show n ++ " :: BitVector" - BV m x + BV n y = bv (same m n) (x + y) - BV m x - BV n y = bv (same m n) (x - y) - BV m x * BV n y = bv (same m n) (x * y) - negate (BV m x) = bv m (- x) + BV s m x + BV _ n y = sbv s (same m n) (x + y) + BV s m x - BV _ n y = sbv s (same m n) (x - y) + BV s m x * BV _ n y = sbv s (same m n) (x * y) + negate (BV s m x) = sbv s m (- x) abs = id - signum (BV m _) = bv m 1 + signum (BV s m _) = sbv s m 1 instance Bits BitVector where - BV m x .&. BV n y = BV (same m n) (x .&. y) - BV m x .|. BV n y = BV (same m n) (x .|. y) - BV m x `xor` BV n y = BV (same m n) (x `xor` y) - complement (BV m x) = BV m (x `xor` bitMask m) - shift (BV m x) i = bv m (shift x i) - rotate (BV m x) i = bv m (shift x j .|. shift x (j - m)) - where j = i `mod` m - bit _i = error "bit: can't determine width" - setBit (BV m x) i = BV m (setBit x i) - clearBit (BV m x) i = BV m (clearBit x i) - complementBit (BV m x) i = BV m (complementBit x i) - testBit (BV _ x) i = testBit x i - bitSize (BV m _) = m - isSigned _ = False - popCount (BV _ x) = popCount x + BV s m x .&. BV _ n y = BV s (same m n) (x .&. y) + BV s m x .|. BV _ n y = BV s (same m n) (x .|. y) + BV s m x `xor` BV _ n y = BV s (same m n) (x `xor` y) + complement (BV s m x) = BV s m (x `xor` bitMask m) + shift (BV s m x) i = sbv s m (shift x i) + rotate (BV s m x) i = sbv s m (shift x j .|. shift x (j - m)) + where j = i `mod` m + bit _i = error "bit: can't determine width" + setBit (BV s m x) i = BV s m (setBit x i) + clearBit (BV s m x) i = BV s m (clearBit x i) + complementBit (BV s m x) i = BV s m (complementBit x i) + testBit (BV _ _ x) i = testBit x i + bitSize (BV _ m _) = m + isSigned (BV s _ _) = s + popCount (BV _ _ x) = popCount x -------------------------------------------------------------------------------- -- SBV class instances @@ -75,12 +97,12 @@ instance Bits BitVector where type SWord = SBV BitVector instance HasKind BitVector where - kindOf (BV w _) = KBounded False w + kindOf (BV s w _) = KBounded s w instance SymWord BitVector where - literal (BV w x) = SBV k (Left (mkConstCW k x)) - where k = KBounded False w - fromCW c@(CW (KBounded False w) _) = BV w (fromCW c) + literal (BV s w x) = SBV k (Left (mkConstCW k x)) + where k = KBounded s w + fromCW c@(CW (KBounded s w) _) = BV s w (fromCW c) fromCW c = error $ "fromCW: Unsupported non-integral value: " ++ show c mkSymWord _ _ = error "mkSymWord unimplemented for type BitVector" @@ -92,10 +114,10 @@ instance FromBits (SBV BitVector) where go !acc !i (x:xs) = go (ite x (setBit acc i) acc) (i+1) xs instance SDivisible BitVector where - sQuotRem (BV m x) (BV n y) = (BV w q, BV w r) + sQuotRem (BV _ m x) (BV _ n y) = (BV False w q, BV False w r) where (q, r) = quotRem x y w = same m n - sDivMod (BV m x) (BV n y) = (BV w q, BV w r) + sDivMod (BV _ m x) (BV _ n y) = (BV False w q, BV False w r) where (q, r) = divMod x y w = same m n @@ -104,7 +126,7 @@ instance SDivisible (SBV BitVector) where sDivMod = liftDMod extract :: Int -> Int -> SWord -> SWord -extract i j x = +extract i j x@(SBV (KBounded s _) _) = case x of _ | i < j -> SBV k (Left (CW k (CWInteger 0))) SBV _ (Left cw) -> @@ -115,7 +137,7 @@ extract i j x = where y st = do sw <- sbvToSW st x newExpr st k (SBVApp (Extract i j) [sw]) where - k = KBounded False (i - j + 1) + k = KBounded s (i - j + 1) cat :: SWord -> SWord -> SWord cat x y | bitSize x == 0 = y From 72fefff3670beed27b3d8c53c12eaaa38438c307 Mon Sep 17 00:00:00 2001 From: Joey Dodds Date: Tue, 5 Aug 2014 14:08:30 -0700 Subject: [PATCH 39/45] added malicious sha example --- examples/maliciousSHA/eve1.sh | 7 + examples/maliciousSHA/eve2.sh | 7 + examples/maliciousSHA/malicious_SHA1.cry | 175 +++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100755 examples/maliciousSHA/eve1.sh create mode 100755 examples/maliciousSHA/eve2.sh create mode 100755 examples/maliciousSHA/malicious_SHA1.cry diff --git a/examples/maliciousSHA/eve1.sh b/examples/maliciousSHA/eve1.sh new file mode 100755 index 00000000..99481e22 --- /dev/null +++ b/examples/maliciousSHA/eve1.sh @@ -0,0 +1,7 @@ +#‘4@ ØM¦ÓTá+¸…[Gx&½ý7+îæP,uKW8¿Ø¥à²D”Q*Í6¢þ⊟2U™ª´zí‚ + +if [ `od -t x1 -j3 -N1 -An "${0}"` -eq "91" ]; then + echo "Cryptol"; +else + echo "Galois"; +fi diff --git a/examples/maliciousSHA/eve2.sh b/examples/maliciousSHA/eve2.sh new file mode 100755 index 00000000..d34a28f1 --- /dev/null +++ b/examples/maliciousSHA/eve2.sh @@ -0,0 +1,7 @@ +#’@ ¬˜M¦Ó¼áIp…ox&¹½7+¬®PjýKU8¿Ì­à²Fº”Q~E6¢~⊟šU™©zíâ + +if [ `od -t x1 -j3 -N1 -An "${0}"` -eq "91" ]; then + echo "Cryptol"; +else + echo "Galois"; +fi diff --git a/examples/maliciousSHA/malicious_SHA1.cry b/examples/maliciousSHA/malicious_SHA1.cry new file mode 100755 index 00000000..a65b7cf6 --- /dev/null +++ b/examples/maliciousSHA/malicious_SHA1.cry @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2004, 2013-2014 Galois, Inc. + * Distributed under the terms of the BSD3 license (see LICENSE file) + */ + +// Description of SHA1 at http://www.itl.nist.gov/fipspubs/fip180-4.htm +/* WARNING: This file represents a collision in a malicious SHA-1. It is modified + * to take initialization as an input. This interface to SHA-1 should not + * be used. The collision presented here, description, and paper can be found + * at http://malicioussha1.github.io/ + * The Malicious SHA-1 project is a joint work of + * Ange Albertini (Corkami, Germany) + * Jean-Philippe Aumasson (Kudelski Security, Switzerland) + * Maria Eichlseder (Graz University of Technology, Austria) + * Florian Mendel (Graz University of Technology, Austria) + * Martin Schlaeffer (Graz University of Technology, Austria) + * + * Research paper at http://malicioussha1.github.io/doc/malsha1.pdf + */ + +malicious_sha1 msg k = malicious_sha1' rmsg k + where + rmsg = pad(join(reverse (groupBy`{8} (join (reverse eve1))))) + +malicious_sha1' : {chunks} (fin chunks) => [chunks][512] -> [4][32] -> [160] +malicious_sha1' pmsg k = join (Hs!0) + where + Hs = [[0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]] # + [ malicious_block (H, split(M)) k + | H <- Hs + | M <- pmsg + ] + +//hexdump of file available at http://malicioussha1.github.io/pocs/eve1.sh +//when executed will print an ascii cow +eve1 = [ + 0x1d23, 0x911b, 0x4034, 0xd809, 0x4d10, 0xd3a6, 0xe154, 0x2b10, + 0x85b8, 0x5b12, 0x7847, 0xbd26, 0x37fd, 0xee2b, 0x50e6, 0x2c08, + 0x4b75, 0x5716, 0x1138, 0xd8bf, 0xe0a5, 0x44b2, 0x941a, 0x2a51, + 0x36cd, 0x04a2, 0xe2fe, 0x9f8a, 0x5532, 0xaa99, 0x7ab4, 0x82ed, + 0x0a0a, 0x6669, 0x5b20, 0x6020, 0x646f, 0x2d20, 0x2074, 0x3178, + 0x2d20, 0x336a, 0x2d20, 0x314e, 0x2d20, 0x6e41, 0x2220, 0x7b24, + 0x7d30, 0x6022, 0x2d20, 0x7165, 0x2220, 0x3139, 0x2022, 0x3b5d, + 0x7420, 0x6568, 0x206e, 0x200a, 0x6520, 0x6863, 0x206f, 0x2022, + 0x2020, 0x2020, 0x2020, 0x2020, 0x5f28, 0x295f, 0x6e5c, 0x2020, + 0x2020, 0x2020, 0x2020, 0x2820, 0x6f6f, 0x5c29, 0x206e, 0x2f20, + 0x2d2d, 0x2d2d, 0x2d2d, 0x5c2d, 0x2f5c, 0x6e5c, 0x2f20, 0x7c20, + 0x2020, 0x2020, 0x7c20, 0x5c7c, 0x2a6e, 0x2020, 0x7c7c, 0x2d2d, + 0x2d2d, 0x7c7c, 0x6e5c, 0x2020, 0x5e20, 0x205e, 0x2020, 0x5e20, + 0x225e, 0x0a3b, 0x6c65, 0x6573, 0x200a, 0x6520, 0x6863, 0x206f, + 0x4822, 0x6c65, 0x6f6c, 0x5720, 0x726f, 0x646c, 0x222e, 0x0a3b, + 0x6966, 0x000a] + +//hexdump of file available at http://malicioussha1.github.io/pocs/eve2.sh +//when executed will print "hello world" +//The ascii cow and hello world can be switched out in both files +//and the hashes will still collide. The next example shows this +eve2 = [ + 0x1d23, 0x921b, 0x4014, 0xac09, 0x4d98, 0xd3a6, 0xe1bc, 0x4910, + 0x8570, 0x1812, 0x786f, 0xb926, 0x37bd, 0xac2b, 0x50ae, 0x6a08, + 0x4bfd, 0x5516, 0x1138, 0xccbf, 0xe0ad, 0x46b2, 0x94ba, 0x7e51, + 0x3645, 0x06a2, 0xe27e, 0x9f8a, 0x559a, 0xa999, 0x7a1c, 0xe2ed, + 0x0a0a, 0x6669, 0x5b20, 0x6020, 0x646f, 0x2d20, 0x2074, 0x3178, + 0x2d20, 0x336a, 0x2d20, 0x314e, 0x2d20, 0x6e41, 0x2220, 0x7b24, + 0x7d30, 0x6022, 0x2d20, 0x7165, 0x2220, 0x3139, 0x2022, 0x3b5d, + 0x7420, 0x6568, 0x206e, 0x200a, 0x6520, 0x6863, 0x206f, 0x2022, + 0x2020, 0x2020, 0x2020, 0x2020, 0x5f28, 0x295f, 0x6e5c, 0x2020, + 0x2020, 0x2020, 0x2020, 0x2820, 0x6f6f, 0x5c29, 0x206e, 0x2f20, + 0x2d2d, 0x2d2d, 0x2d2d, 0x5c2d, 0x2f5c, 0x6e5c, 0x2f20, 0x7c20, + 0x2020, 0x2020, 0x7c20, 0x5c7c, 0x2a6e, 0x2020, 0x7c7c, 0x2d2d, + 0x2d2d, 0x7c7c, 0x6e5c, 0x2020, 0x5e20, 0x205e, 0x2020, 0x5e20, + 0x225e, 0x0a3b, 0x6c65, 0x6573, 0x200a, 0x6520, 0x6863, 0x206f, + 0x4822, 0x6c65, 0x6f6c, 0x5720, 0x726f, 0x646c, 0x222e, 0x0a3b, + 0x6966, 0x000a] + +//K from proof-of-concept section of http://malicioussha1.github.io/#downloads +malicious_k1 = [0x5a827999, 0x88e8ea68, 0x578059de, 0x54324a39] + +bad_sha_eve1 = malicious_sha1 eve1 malicious_k1 +bad_sha_eve2 = malicious_sha1 eve2 malicious_k1 +property malicious_sha1_collision1 = bad_sha_eve1 == bad_sha_eve2 + +//hexdump malicious/eve1.sh +eve1_galois = [ + 0x1d23, 0x911b, 0x4034, 0xd809, 0x4d10, 0xd3a6, 0xe154, 0x2b10, + 0x85b8, 0x5b12, 0x7847, 0xbd26, 0x37fd, 0xee2b, 0x50e6, 0x2c08, + 0x4b75, 0x5716, 0x1138, 0xd8bf, 0xe0a5, 0x44b2, 0x941a, 0x2a51, + 0x36cd, 0x04a2, 0xe2fe, 0x9f8a, 0x5532, 0xaa99, 0x7ab4, 0x82ed, + 0x0a0a, 0x6669, 0x5b20, 0x6020, 0x646f, 0x2d20, 0x2074, 0x3178, + 0x2d20, 0x336a, 0x2d20, 0x314e, 0x2d20, 0x6e41, 0x2220, 0x7b24, + 0x7d30, 0x6022, 0x2d20, 0x7165, 0x2220, 0x3139, 0x2022, 0x3b5d, + 0x7420, 0x6568, 0x206e, 0x200a, 0x6520, 0x6863, 0x206f, 0x4322, + 0x7972, 0x7470, 0x6c6f, 0x3b22, 0x650a, 0x736c, 0x0a65, 0x2020, + 0x6365, 0x6f68, 0x2220, 0x6147, 0x6f6c, 0x7369, 0x3b22, 0x660a, + 0x0a69] + +//hexdump malicious/eve1.sh +eve2_galois = [ + 0x1d23, 0x921b, 0x4014, 0xac09, 0x4d98, 0xd3a6, 0xe1bc, 0x4910, + 0x8570, 0x1812, 0x786f, 0xb926, 0x37bd, 0xac2b, 0x50ae, 0x6a08, + 0x4bfd, 0x5516, 0x1138, 0xccbf, 0xe0ad, 0x46b2, 0x94ba, 0x7e51, + 0x3645, 0x06a2, 0xe27e, 0x9f8a, 0x559a, 0xa999, 0x7a1c, 0xe2ed, + 0x0a0a, 0x6669, 0x5b20, 0x6020, 0x646f, 0x2d20, 0x2074, 0x3178, + 0x2d20, 0x336a, 0x2d20, 0x314e, 0x2d20, 0x6e41, 0x2220, 0x7b24, + 0x7d30, 0x6022, 0x2d20, 0x7165, 0x2220, 0x3139, 0x2022, 0x3b5d, + 0x7420, 0x6568, 0x206e, 0x200a, 0x6520, 0x6863, 0x206f, 0x4322, + 0x7972, 0x7470, 0x6c6f, 0x3b22, 0x650a, 0x736c, 0x0a65, 0x2020, + 0x6365, 0x6f68, 0x2220, 0x6147, 0x6f6c, 0x7369, 0x3b22, 0x660a, + 0x0a69] + +bad_sha_eve_galois1 = malicious_sha1 eve1_galois malicious_k1 +bad_sha_eve_galois2 = malicious_sha1 eve2_galois malicious_k1 + +property malicious_sha1_collision2 = bad_sha_eve_galois1 == bad_sha_eve_galois2 + +property all_same_hashes = bad_sha_eve_galois1 == bad_sha_eve1 && malicious_sha1_collision1 && malicious_sha1_collision2 + +/* +As a summary, a "1" followed by m "0"s followed by a 64- + bit integer are appended to the end of the message to produce a + padded message of length 512 * n. The 64-bit integer is the length + of the original message. The padded message is then processed by the + SHA-1 as n 512-bit blocks. +*/ + +pad : {msgLen,contentLen,chunks,padding} + ( fin msgLen + , 64 >= width msgLen // message width fits in a word + , contentLen == msgLen + 65 // message + header + , chunks == (contentLen+511) / 512 + , padding == (512 - contentLen % 512) % 512 // prettier if type #'s could be < 0 + , msgLen == 512 * chunks - (65 + padding) // redundant, but Cryptol can't yet do the math + ) + => [msgLen] -> [chunks][512] +pad msg = split (msg # [True] # (zero:[padding]) # (`msgLen:[64])) + +f : ([8], [32], [32], [32]) -> [32] +f (t, x, y, z) = + if (0 <= t) && (t <= 19) then (x && y) ^ (~x && z) + | (20 <= t) && (t <= 39) then x ^ y ^ z + | (40 <= t) && (t <= 59) then (x && y) ^ (x && z) ^ (y && z) + | (60 <= t) && (t <= 79) then x ^ y ^ z + else error "f: t out of range" + + +Ks : [4][32] -> [80][32] +Ks k = [ k@0 | t <- [0..19] ] + # [ k@1 | t <- [20..39] ] + # [ k@2 | t <- [40..59] ] + # [ k@3 | t <- [60..79] ] + +malicious_block : ([5][32], [16][32]) -> [4][32]-> [5][32] +malicious_block ([H0, H1, H2, H3, H4], M) k = + [(H0+As@80), (H1+Bs@80), (H2+Cs@80), (H3+Ds@80), (H4+Es@80)] + where + Ws : [80][32] + Ws = M # [ (W3 ^ W8 ^ W14 ^ W16) <<< 1 + | W16 <- drop`{16 - 16} Ws + | W14 <- drop`{16 - 14} Ws + | W8 <- drop`{16 - 8} Ws + | W3 <- drop`{16 - 3} Ws + | t <- [16..79] + ] + As = [H0] # TEMP + Bs = [H1] # As + Cs = [H2] # [ B <<< 30 | B <- Bs ] + Ds = [H3] # Cs + Es = [H4] # Ds + TEMP : [80][32] + TEMP = [ (A <<< 5) + f(t, B, C, D) + E + W + K + | A <- As | B <- Bs | C <- Cs | D <- Ds | E <- Es + | W <- Ws | K <- (Ks k) + | t <- [0..79] + ] + From abe336d907f559bd527cb1e218ffacc34d09d9f0 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 6 Aug 2014 09:49:19 -0700 Subject: [PATCH 40/45] Added SignCast instance and exported sbv Changes necessary for SAWCore's use of SBV. Ultimately, these should be merged into the upstream SBV package. --- cryptol.cabal | 8 +-- src/Cryptol/Symbolic/BitVector.hs | 90 +++++++++++++++++++------------ 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/cryptol.cabal b/cryptol.cabal index 91ec2a39..ac8d82eb 100644 --- a/cryptol.cabal +++ b/cryptol.cabal @@ -114,9 +114,6 @@ library Cryptol.Symbolic.Prims Cryptol.Symbolic.Value - - Other-modules: Cryptol.Parser.LexerUtils, - Cryptol.Parser.ParserUtils, Data.SBV, Data.SBV.Bridge.Boolector, Data.SBV.Bridge.CVC4, @@ -124,6 +121,10 @@ library Data.SBV.Bridge.Yices, Data.SBV.Bridge.Z3, Data.SBV.Internals, + Data.SBV.Tools.Polynomial + + Other-modules: Cryptol.Parser.LexerUtils, + Cryptol.Parser.ParserUtils, Data.SBV.BitVectors.AlgReals, Data.SBV.BitVectors.Data, Data.SBV.BitVectors.Model, @@ -147,7 +148,6 @@ library Data.SBV.Tools.ExpectedValue, Data.SBV.Tools.GenTest, Data.SBV.Tools.Optimize, - Data.SBV.Tools.Polynomial, Data.SBV.Utils.Boolean, Data.SBV.Utils.TDiff, Data.SBV.Utils.Lib, diff --git a/src/Cryptol/Symbolic/BitVector.hs b/src/Cryptol/Symbolic/BitVector.hs index 5b007749..0d49f21b 100644 --- a/src/Cryptol/Symbolic/BitVector.hs +++ b/src/Cryptol/Symbolic/BitVector.hs @@ -8,6 +8,8 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} module Cryptol.Symbolic.BitVector where @@ -21,7 +23,7 @@ import Data.SBV.BitVectors.Data -- BitVector type -------------------------------------------------------------- -data BitVector = BV { width :: !Int, val :: !Integer } +data BitVector = BV { signedcxt :: Bool, width :: !Int, val :: !Integer } deriving (Eq, Ord, Show) -- ^ Invariant: BV w x requires that 0 <= w and 0 <= x < 2^w. @@ -30,44 +32,64 @@ bitMask w = bit w - 1 -- | Smart constructor for bitvectors. bv :: Int -> Integer -> BitVector -bv w x = BV w (x .&. bitMask w) +bv = sbv False -unsigned :: BitVector -> Integer -unsigned = val +sbv :: Bool -> Int -> Integer -> BitVector +sbv b w x = BV b w (x .&. bitMask w) -signed :: BitVector -> Integer -signed (BV w x) +unsigned :: Int -> Integer -> Integer +unsigned w x = x + bit w + +signed :: Int -> Integer -> Integer +signed w x | w > 0 && testBit x (w - 1) = x - bit w | otherwise = x same :: Int -> Int -> Int same m n = if m == n then m else error $ "BitVector size mismatch: " ++ show (m, n) +instance SignCast SWord SWord where + signCast (SBV (KBounded _ w) (Left (cwVal -> (CWInteger x)))) = + SBV k (Left (CW k (CWInteger (signed w x)))) where + k = KBounded True w + signCast x@(SBV (KBounded _ w) _) = SBV k (Right (cache y)) where + k = KBounded True w + y st = do xsw <- sbvToSW st x + newExpr st k (SBVApp (Extract (intSizeOf x-1) 0) [xsw]) + signCast _ = error "SignCast not called on BitVector value" + unsignCast (SBV (KBounded _ w) (Left (cwVal -> (CWInteger x)))) = + SBV k (Left (CW k (CWInteger (unsigned w x)))) where + k = KBounded False w + unsignCast x@(SBV (KBounded _ w) _) = SBV k (Right (cache y)) where + k = KBounded False w + y st = do xsw <- sbvToSW st x + newExpr st k (SBVApp (Extract (intSizeOf x-1) 0) [xsw]) + instance Num BitVector where fromInteger n = error $ "fromInteger " ++ show n ++ " :: BitVector" - BV m x + BV n y = bv (same m n) (x + y) - BV m x - BV n y = bv (same m n) (x - y) - BV m x * BV n y = bv (same m n) (x * y) - negate (BV m x) = bv m (- x) + BV s m x + BV _ n y = sbv s (same m n) (x + y) + BV s m x - BV _ n y = sbv s (same m n) (x - y) + BV s m x * BV _ n y = sbv s (same m n) (x * y) + negate (BV s m x) = sbv s m (- x) abs = id - signum (BV m _) = bv m 1 + signum (BV s m _) = sbv s m 1 instance Bits BitVector where - BV m x .&. BV n y = BV (same m n) (x .&. y) - BV m x .|. BV n y = BV (same m n) (x .|. y) - BV m x `xor` BV n y = BV (same m n) (x `xor` y) - complement (BV m x) = BV m (x `xor` bitMask m) - shift (BV m x) i = bv m (shift x i) - rotate (BV m x) i = bv m (shift x j .|. shift x (j - m)) - where j = i `mod` m - bit _i = error "bit: can't determine width" - setBit (BV m x) i = BV m (setBit x i) - clearBit (BV m x) i = BV m (clearBit x i) - complementBit (BV m x) i = BV m (complementBit x i) - testBit (BV _ x) i = testBit x i - bitSize (BV m _) = m - isSigned _ = False - popCount (BV _ x) = popCount x + BV s m x .&. BV _ n y = BV s (same m n) (x .&. y) + BV s m x .|. BV _ n y = BV s (same m n) (x .|. y) + BV s m x `xor` BV _ n y = BV s (same m n) (x `xor` y) + complement (BV s m x) = BV s m (x `xor` bitMask m) + shift (BV s m x) i = sbv s m (shift x i) + rotate (BV s m x) i = sbv s m (shift x j .|. shift x (j - m)) + where j = i `mod` m + bit _i = error "bit: can't determine width" + setBit (BV s m x) i = BV s m (setBit x i) + clearBit (BV s m x) i = BV s m (clearBit x i) + complementBit (BV s m x) i = BV s m (complementBit x i) + testBit (BV _ _ x) i = testBit x i + bitSize (BV _ m _) = m + isSigned (BV s _ _) = s + popCount (BV _ _ x) = popCount x -------------------------------------------------------------------------------- -- SBV class instances @@ -75,12 +97,12 @@ instance Bits BitVector where type SWord = SBV BitVector instance HasKind BitVector where - kindOf (BV w _) = KBounded False w + kindOf (BV s w _) = KBounded s w instance SymWord BitVector where - literal (BV w x) = SBV k (Left (mkConstCW k x)) - where k = KBounded False w - fromCW c@(CW (KBounded False w) _) = BV w (fromCW c) + literal (BV s w x) = SBV k (Left (mkConstCW k x)) + where k = KBounded s w + fromCW c@(CW (KBounded s w) _) = BV s w (fromCW c) fromCW c = error $ "fromCW: Unsupported non-integral value: " ++ show c mkSymWord _ _ = error "mkSymWord unimplemented for type BitVector" @@ -92,10 +114,10 @@ instance FromBits (SBV BitVector) where go !acc !i (x:xs) = go (ite x (setBit acc i) acc) (i+1) xs instance SDivisible BitVector where - sQuotRem (BV m x) (BV n y) = (BV w q, BV w r) + sQuotRem (BV _ m x) (BV _ n y) = (BV False w q, BV False w r) where (q, r) = quotRem x y w = same m n - sDivMod (BV m x) (BV n y) = (BV w q, BV w r) + sDivMod (BV _ m x) (BV _ n y) = (BV False w q, BV False w r) where (q, r) = divMod x y w = same m n @@ -104,7 +126,7 @@ instance SDivisible (SBV BitVector) where sDivMod = liftDMod extract :: Int -> Int -> SWord -> SWord -extract i j x = +extract i j x@(SBV (KBounded s _) _) = case x of _ | i < j -> SBV k (Left (CW k (CWInteger 0))) SBV _ (Left cw) -> @@ -115,7 +137,7 @@ extract i j x = where y st = do sw <- sbvToSW st x newExpr st k (SBVApp (Extract i j) [sw]) where - k = KBounded False (i - j + 1) + k = KBounded s (i - j + 1) cat :: SWord -> SWord -> SWord cat x y | bitSize x == 0 = y From b73620a63b23094a49fe5bcd312df379540719f0 Mon Sep 17 00:00:00 2001 From: Sam Anklesaria Date: Wed, 6 Aug 2014 10:14:53 -0700 Subject: [PATCH 41/45] mkUninterpret function added to sbv Model --- sbv/Data/SBV/BitVectors/Model.hs | 119 +++++++++++++++++-------------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/sbv/Data/SBV/BitVectors/Model.hs b/sbv/Data/SBV/BitVectors/Model.hs index 6b8c8314..2ae6a095 100644 --- a/sbv/Data/SBV/BitVectors/Model.hs +++ b/sbv/Data/SBV/BitVectors/Model.hs @@ -1482,6 +1482,8 @@ class Uninterpreted a where -- | Uninterpret a value, receiving an object that can be used instead. Use this version -- when you do not need to add an axiom about this value. uninterpret :: String -> a + -- | Uninterpret a value of the given SMTLib type (interpreting (:) as (->)). + mkUninterpreted :: [Kind] -> Maybe ([String], a) -> String -> a -- | Uninterpret a value, only for the purposes of code-generation. For execution -- and verification the value is used as is. For code-generation, the alternate -- definition is used. This is useful when we want to take advantage of native @@ -1497,43 +1499,46 @@ class Uninterpreted a where -- Plain constants instance HasKind a => Uninterpreted (SBV a) where - sbvUninterpret mbCgData nm + sbvUninterpret = mkUninterpreted [kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm | Just (_, v) <- mbCgData = v | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st v - | True = do newUninterpreted st nm (SBVType [ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) newExpr st ka $ SBVApp (Uninterpreted nm) [] -- Functions of one argument instance (SymWord b, HasKind a) => Uninterpreted (SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [kindOf (undefined :: b), kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 | Just (_, v) <- mbCgData, isConcrete arg0 = v arg0 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0) - | True = do newUninterpreted st nm (SBVType [kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 mapM_ forceSWArg [sw0] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0] -- Functions of two arguments instance (SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV c -> SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [ + kindOf (undefined :: c), + kindOf (undefined :: b), + kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 arg1 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1 = v arg0 arg1 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) - kc = kindOf (undefined :: c) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1) - | True = do newUninterpreted st nm (SBVType [kc, kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 mapM_ forceSWArg [sw0, sw1] @@ -1541,18 +1546,20 @@ instance (SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV c -> SBV b -> S -- Functions of three arguments instance (SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [ + kindOf (undefined :: d), + kindOf (undefined :: c), + kindOf (undefined :: b), + kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 arg1 arg2 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2 = v arg0 arg1 arg2 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) - kc = kindOf (undefined :: c) - kd = kindOf (undefined :: d) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2) - | True = do newUninterpreted st nm (SBVType [kd, kc, kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1561,19 +1568,21 @@ instance (SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV d -> -- Functions of four arguments instance (SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [ + kindOf (undefined :: e), + kindOf (undefined :: d), + kindOf (undefined :: c), + kindOf (undefined :: b), + kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 arg1 arg2 arg3 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3 = v arg0 arg1 arg2 arg3 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) - kc = kindOf (undefined :: c) - kd = kindOf (undefined :: d) - ke = kindOf (undefined :: e) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3) - | True = do newUninterpreted st nm (SBVType [ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1583,20 +1592,22 @@ instance (SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterprete -- Functions of five arguments instance (SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV f -> SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [ + kindOf (undefined :: f), + kindOf (undefined :: e), + kindOf (undefined :: d), + kindOf (undefined :: c), + kindOf (undefined :: b), + kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 arg1 arg2 arg3 arg4 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3, isConcrete arg4 = v arg0 arg1 arg2 arg3 arg4 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) - kc = kindOf (undefined :: c) - kd = kindOf (undefined :: d) - ke = kindOf (undefined :: e) - kf = kindOf (undefined :: f) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3 arg4) - | True = do newUninterpreted st nm (SBVType [kf, ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1607,21 +1618,23 @@ instance (SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => U -- Functions of six arguments instance (SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV g -> SBV f -> SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [ + kindOf (undefined :: g), + kindOf (undefined :: f), + kindOf (undefined :: e), + kindOf (undefined :: d), + kindOf (undefined :: c), + kindOf (undefined :: b), + kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 arg1 arg2 arg3 arg4 arg5 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3, isConcrete arg4, isConcrete arg5 = v arg0 arg1 arg2 arg3 arg4 arg5 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) - kc = kindOf (undefined :: c) - kd = kindOf (undefined :: d) - ke = kindOf (undefined :: e) - kf = kindOf (undefined :: f) - kg = kindOf (undefined :: g) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3 arg4 arg5) - | True = do newUninterpreted st nm (SBVType [kg, kf, ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1634,22 +1647,24 @@ instance (SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasK -- Functions of seven arguments instance (SymWord h, SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV h -> SBV g -> SBV f -> SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret mbCgData nm = f + sbvUninterpret = mkUninterpreted [ + kindOf (undefined :: h), + kindOf (undefined :: g), + kindOf (undefined :: f), + kindOf (undefined :: e), + kindOf (undefined :: d), + kindOf (undefined :: c), + kindOf (undefined :: b), + kindOf (undefined :: a)] + mkUninterpreted ks mbCgData nm = f where f arg0 arg1 arg2 arg3 arg4 arg5 arg6 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3, isConcrete arg4, isConcrete arg5, isConcrete arg6 = v arg0 arg1 arg2 arg3 arg4 arg5 arg6 | True = SBV ka $ Right $ cache result - where ka = kindOf (undefined :: a) - kb = kindOf (undefined :: b) - kc = kindOf (undefined :: c) - kd = kindOf (undefined :: d) - ke = kindOf (undefined :: e) - kf = kindOf (undefined :: f) - kg = kindOf (undefined :: g) - kh = kindOf (undefined :: h) + where ka = last ks result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3 arg4 arg5 arg6) - | True = do newUninterpreted st nm (SBVType [kh, kg, kf, ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 From 7300f2960632daea869acacbaf2302d81d4c300b Mon Sep 17 00:00:00 2001 From: Joey Dodds Date: Wed, 6 Aug 2014 10:16:14 -0700 Subject: [PATCH 42/45] changed collision properties to require inputs to be different --- examples/maliciousSHA/malicious_SHA1.cry | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/maliciousSHA/malicious_SHA1.cry b/examples/maliciousSHA/malicious_SHA1.cry index a65b7cf6..0a66622d 100755 --- a/examples/maliciousSHA/malicious_SHA1.cry +++ b/examples/maliciousSHA/malicious_SHA1.cry @@ -78,7 +78,7 @@ malicious_k1 = [0x5a827999, 0x88e8ea68, 0x578059de, 0x54324a39] bad_sha_eve1 = malicious_sha1 eve1 malicious_k1 bad_sha_eve2 = malicious_sha1 eve2 malicious_k1 -property malicious_sha1_collision1 = bad_sha_eve1 == bad_sha_eve2 +property malicious_sha1_collision1 = eve1 != eve2 && bad_sha_eve1 == bad_sha_eve2 //hexdump malicious/eve1.sh eve1_galois = [ @@ -111,7 +111,7 @@ eve2_galois = [ bad_sha_eve_galois1 = malicious_sha1 eve1_galois malicious_k1 bad_sha_eve_galois2 = malicious_sha1 eve2_galois malicious_k1 -property malicious_sha1_collision2 = bad_sha_eve_galois1 == bad_sha_eve_galois2 +property malicious_sha1_collision2 = eve1_galois != eve2_galois && bad_sha_eve_galois1 == bad_sha_eve_galois2 property all_same_hashes = bad_sha_eve_galois1 == bad_sha_eve1 && malicious_sha1_collision1 && malicious_sha1_collision2 From 5fca89f8b1fcacf9e142318f2578717bd783b549 Mon Sep 17 00:00:00 2001 From: Sam Anklesaria Date: Wed, 6 Aug 2014 10:25:20 -0700 Subject: [PATCH 43/45] Revert "mkUninterpret function added to sbv Model" This reverts commit b73620a63b23094a49fe5bcd312df379540719f0. --- sbv/Data/SBV/BitVectors/Model.hs | 119 ++++++++++++++----------------- 1 file changed, 52 insertions(+), 67 deletions(-) diff --git a/sbv/Data/SBV/BitVectors/Model.hs b/sbv/Data/SBV/BitVectors/Model.hs index 2ae6a095..6b8c8314 100644 --- a/sbv/Data/SBV/BitVectors/Model.hs +++ b/sbv/Data/SBV/BitVectors/Model.hs @@ -1482,8 +1482,6 @@ class Uninterpreted a where -- | Uninterpret a value, receiving an object that can be used instead. Use this version -- when you do not need to add an axiom about this value. uninterpret :: String -> a - -- | Uninterpret a value of the given SMTLib type (interpreting (:) as (->)). - mkUninterpreted :: [Kind] -> Maybe ([String], a) -> String -> a -- | Uninterpret a value, only for the purposes of code-generation. For execution -- and verification the value is used as is. For code-generation, the alternate -- definition is used. This is useful when we want to take advantage of native @@ -1499,46 +1497,43 @@ class Uninterpreted a where -- Plain constants instance HasKind a => Uninterpreted (SBV a) where - sbvUninterpret = mkUninterpreted [kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm + sbvUninterpret mbCgData nm | Just (_, v) <- mbCgData = v | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st v - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [ka]) (fst `fmap` mbCgData) newExpr st ka $ SBVApp (Uninterpreted nm) [] -- Functions of one argument instance (SymWord b, HasKind a) => Uninterpreted (SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [kindOf (undefined :: b), kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 | Just (_, v) <- mbCgData, isConcrete arg0 = v arg0 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 mapM_ forceSWArg [sw0] newExpr st ka $ SBVApp (Uninterpreted nm) [sw0] -- Functions of two arguments instance (SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV c -> SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [ - kindOf (undefined :: c), - kindOf (undefined :: b), - kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 arg1 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1 = v arg0 arg1 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) + kc = kindOf (undefined :: c) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 mapM_ forceSWArg [sw0, sw1] @@ -1546,20 +1541,18 @@ instance (SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV c -> SBV b -> S -- Functions of three arguments instance (SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [ - kindOf (undefined :: d), - kindOf (undefined :: c), - kindOf (undefined :: b), - kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 arg1 arg2 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2 = v arg0 arg1 arg2 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) + kc = kindOf (undefined :: c) + kd = kindOf (undefined :: d) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [kd, kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1568,21 +1561,19 @@ instance (SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV d -> -- Functions of four arguments instance (SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [ - kindOf (undefined :: e), - kindOf (undefined :: d), - kindOf (undefined :: c), - kindOf (undefined :: b), - kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 arg1 arg2 arg3 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3 = v arg0 arg1 arg2 arg3 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) + kc = kindOf (undefined :: c) + kd = kindOf (undefined :: d) + ke = kindOf (undefined :: e) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1592,22 +1583,20 @@ instance (SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterprete -- Functions of five arguments instance (SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV f -> SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [ - kindOf (undefined :: f), - kindOf (undefined :: e), - kindOf (undefined :: d), - kindOf (undefined :: c), - kindOf (undefined :: b), - kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 arg1 arg2 arg3 arg4 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3, isConcrete arg4 = v arg0 arg1 arg2 arg3 arg4 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) + kc = kindOf (undefined :: c) + kd = kindOf (undefined :: d) + ke = kindOf (undefined :: e) + kf = kindOf (undefined :: f) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3 arg4) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [kf, ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1618,23 +1607,21 @@ instance (SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => U -- Functions of six arguments instance (SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV g -> SBV f -> SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [ - kindOf (undefined :: g), - kindOf (undefined :: f), - kindOf (undefined :: e), - kindOf (undefined :: d), - kindOf (undefined :: c), - kindOf (undefined :: b), - kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 arg1 arg2 arg3 arg4 arg5 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3, isConcrete arg4, isConcrete arg5 = v arg0 arg1 arg2 arg3 arg4 arg5 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) + kc = kindOf (undefined :: c) + kd = kindOf (undefined :: d) + ke = kindOf (undefined :: e) + kf = kindOf (undefined :: f) + kg = kindOf (undefined :: g) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3 arg4 arg5) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [kg, kf, ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 @@ -1647,24 +1634,22 @@ instance (SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasK -- Functions of seven arguments instance (SymWord h, SymWord g, SymWord f, SymWord e, SymWord d, SymWord c, SymWord b, HasKind a) => Uninterpreted (SBV h -> SBV g -> SBV f -> SBV e -> SBV d -> SBV c -> SBV b -> SBV a) where - sbvUninterpret = mkUninterpreted [ - kindOf (undefined :: h), - kindOf (undefined :: g), - kindOf (undefined :: f), - kindOf (undefined :: e), - kindOf (undefined :: d), - kindOf (undefined :: c), - kindOf (undefined :: b), - kindOf (undefined :: a)] - mkUninterpreted ks mbCgData nm = f + sbvUninterpret mbCgData nm = f where f arg0 arg1 arg2 arg3 arg4 arg5 arg6 | Just (_, v) <- mbCgData, isConcrete arg0, isConcrete arg1, isConcrete arg2, isConcrete arg3, isConcrete arg4, isConcrete arg5, isConcrete arg6 = v arg0 arg1 arg2 arg3 arg4 arg5 arg6 | True = SBV ka $ Right $ cache result - where ka = last ks + where ka = kindOf (undefined :: a) + kb = kindOf (undefined :: b) + kc = kindOf (undefined :: c) + kd = kindOf (undefined :: d) + ke = kindOf (undefined :: e) + kf = kindOf (undefined :: f) + kg = kindOf (undefined :: g) + kh = kindOf (undefined :: h) result st | Just (_, v) <- mbCgData, inProofMode st = sbvToSW st (v arg0 arg1 arg2 arg3 arg4 arg5 arg6) - | True = do newUninterpreted st nm (SBVType ks) (fst `fmap` mbCgData) + | True = do newUninterpreted st nm (SBVType [kh, kg, kf, ke, kd, kc, kb, ka]) (fst `fmap` mbCgData) sw0 <- sbvToSW st arg0 sw1 <- sbvToSW st arg1 sw2 <- sbvToSW st arg2 From 8e547756424469e365935496235aaad3c0eed90e Mon Sep 17 00:00:00 2001 From: Sam Anklesaria Date: Thu, 7 Aug 2014 10:54:40 -0700 Subject: [PATCH 44/45] exposing low level uninterpreted interface --- sbv/Data/SBV/BitVectors/Model.hs | 11 ++++++++++- sbv/Data/SBV/Internals.hs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sbv/Data/SBV/BitVectors/Model.hs b/sbv/Data/SBV/BitVectors/Model.hs index 6b8c8314..fb83889b 100644 --- a/sbv/Data/SBV/BitVectors/Model.hs +++ b/sbv/Data/SBV/BitVectors/Model.hs @@ -23,7 +23,7 @@ module Data.SBV.BitVectors.Model ( Mergeable(..), EqSymbolic(..), OrdSymbolic(..), SDivisible(..), Uninterpreted(..), SIntegral , sbvTestBit, sbvPopCount, setBitTo, sbvShiftLeft, sbvShiftRight, sbvSignedShiftArithRight - , sbvRotateLeft, sbvRotateRight + , sbvRotateLeft, sbvRotateRight, mkUninterpreted , allEqual, allDifferent, inRange, sElem, oneIf, blastBE, blastLE, fullAdder, fullMultiplier , lsb, msb, genVar, genVar_, forall, forall_, exists, exists_ , constrain, pConstrain, sBool, sBools, sWord8, sWord8s, sWord16, sWord16s, sWord32 @@ -1495,6 +1495,15 @@ class Uninterpreted a where uninterpret = sbvUninterpret Nothing cgUninterpret nm code v = sbvUninterpret (Just (code, v)) nm +mkUninterpreted :: [Kind] -> [SBV ()] -> String -> SBV a +mkUninterpreted ks args nm = SBV ka $ Right $ cache result where + ka = last ks + result st = do + newUninterpreted st nm (SBVType ks) Nothing + sws <- mapM (sbvToSW st) args + mapM_ forceSWArg sws + newExpr st ka $ SBVApp (Uninterpreted nm) sws + -- Plain constants instance HasKind a => Uninterpreted (SBV a) where sbvUninterpret mbCgData nm diff --git a/sbv/Data/SBV/Internals.hs b/sbv/Data/SBV/Internals.hs index 7c9bd8f6..a07ce91a 100644 --- a/sbv/Data/SBV/Internals.hs +++ b/sbv/Data/SBV/Internals.hs @@ -18,10 +18,10 @@ module Data.SBV.Internals ( , SBV(..), slet, CW(..), Kind(..), CWVal(..), AlgReal(..), mkConstCW, genVar, genVar_ , liftQRem, liftDMod -- * Compilation to C - , compileToC', compileToCLib', CgPgmBundle(..), CgPgmKind(..) + , mkUninterpreted, compileToC', compileToCLib', CgPgmBundle(..), CgPgmKind(..) ) where import Data.SBV.BitVectors.Data (Result, SBVRunMode(..), runSymbolic, runSymbolic', SBV(..), CW(..), Kind(..), CWVal(..), AlgReal(..), mkConstCW) -import Data.SBV.BitVectors.Model (genVar, genVar_, slet, liftQRem, liftDMod) +import Data.SBV.BitVectors.Model (genVar, genVar_, slet, liftQRem, liftDMod, mkUninterpreted) import Data.SBV.Compilers.C (compileToC', compileToCLib') import Data.SBV.Compilers.CodeGen (CgPgmBundle(..), CgPgmKind(..)) From fdaff29e9ddc053d5a74b755790d36aa7b34efb7 Mon Sep 17 00:00:00 2001 From: Brian Huffman Date: Fri, 15 Aug 2014 16:18:14 -0700 Subject: [PATCH 45/45] Support for loading multiple source files simultaneously - track source filepath for each loaded module - raise error when different loaded files use same module name --- src/Cryptol/ModuleSystem.hs | 6 +++--- src/Cryptol/ModuleSystem/Base.hs | 20 +++++++++++++++----- src/Cryptol/ModuleSystem/Env.hs | 6 ++++-- src/Cryptol/ModuleSystem/Monad.hs | 18 +++++++++++++++--- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/Cryptol/ModuleSystem.hs b/src/Cryptol/ModuleSystem.hs index 2712be17..3111001c 100644 --- a/src/Cryptol/ModuleSystem.hs +++ b/src/Cryptol/ModuleSystem.hs @@ -52,10 +52,10 @@ loadModuleByPath path = do return m -- | Load the given parsed module. -loadModule :: P.Module -> ModuleCmd T.Module -loadModule m env = runModuleM env $ do +loadModule :: FilePath -> P.Module -> ModuleCmd T.Module +loadModule path m env = runModuleM env $ do let n = P.thing (P.mName m) - m' <- loadingModule n (Base.loadModule m) + m' <- loadingModule n (Base.loadModule path m) setFocusedModule (T.mName m') return m' diff --git a/src/Cryptol/ModuleSystem/Base.hs b/src/Cryptol/ModuleSystem/Base.hs index c516378b..897514a7 100644 --- a/src/Cryptol/ModuleSystem/Base.hs +++ b/src/Cryptol/ModuleSystem/Base.hs @@ -10,6 +10,7 @@ module Cryptol.ModuleSystem.Base where import Cryptol.ModuleSystem.Interface import Cryptol.ModuleSystem.Monad +import Cryptol.ModuleSystem.Env (lookupModule, LoadedModule(..)) import qualified Cryptol.Eval as E import qualified Cryptol.Eval.Value as E import qualified Cryptol.ModuleSystem.Renamer as R @@ -104,7 +105,16 @@ loadModuleByPath :: FilePath -> ModuleM T.Module loadModuleByPath path = do pm <- parseModule path let n = thing (P.mName pm) - loadingModule n (loadModule pm) + + -- Check whether this module name has already been loaded from a different file + env <- getModuleEnv + case lookupModule n env of + Nothing -> loadingModule n (loadModule path pm) + Just lm + | path == path' -> return (lmModule lm) + | otherwise -> duplicateModuleName n path path' + where path' = lmFilePath lm + -- | Load the module specified by an import. loadImport :: Located P.Import -> ModuleM () @@ -122,12 +132,12 @@ loadImport li = do -- make sure that this module is the one we expect unless (n == thing (P.mName pm)) (moduleNameMismatch n (mName pm)) - _ <- loadModule pm + _ <- loadModule path pm return () -- | Load dependencies, typecheck, and add to the eval environment. -loadModule :: P.Module -> ModuleM T.Module -loadModule pm = do +loadModule :: FilePath -> P.Module -> ModuleM T.Module +loadModule path pm = do let pm' = addPrelude pm loadDeps pm' @@ -140,7 +150,7 @@ loadModule pm = do -- extend the eval env modifyEvalEnv (E.moduleEnv tcm) - loadedModule tcm + loadedModule path tcm return tcm diff --git a/src/Cryptol/ModuleSystem/Env.hs b/src/Cryptol/ModuleSystem/Env.hs index 42a61d0d..6d975506 100644 --- a/src/Cryptol/ModuleSystem/Env.hs +++ b/src/Cryptol/ModuleSystem/Env.hs @@ -106,6 +106,7 @@ instance Monoid LoadedModules where data LoadedModule = LoadedModule { lmName :: ModName + , lmFilePath :: FilePath , lmInterface :: Iface , lmModule :: T.Module } deriving (Show) @@ -116,13 +117,14 @@ isLoaded mn lm = any ((mn ==) . lmName) (getLoadedModules lm) lookupModule :: ModName -> ModuleEnv -> Maybe LoadedModule lookupModule mn env = List.find ((mn ==) . lmName) (getLoadedModules (meLoadedModules env)) -addLoadedModule :: T.Module -> LoadedModules -> LoadedModules -addLoadedModule tm lm +addLoadedModule :: FilePath -> T.Module -> LoadedModules -> LoadedModules +addLoadedModule path tm lm | isLoaded (T.mName tm) lm = lm | otherwise = LoadedModules (getLoadedModules lm ++ [loaded]) where loaded = LoadedModule { lmName = T.mName tm + , lmFilePath = path , lmInterface = genIface tm , lmModule = tm } diff --git a/src/Cryptol/ModuleSystem/Monad.hs b/src/Cryptol/ModuleSystem/Monad.hs index 07ad3acb..a5fc71bc 100644 --- a/src/Cryptol/ModuleSystem/Monad.hs +++ b/src/Cryptol/ModuleSystem/Monad.hs @@ -72,6 +72,9 @@ data ModuleError | OtherFailure String -- ^ Problems after type checking, eg. specialization | ModuleNameMismatch P.ModName (Located P.ModName) + -- ^ Module loaded by 'import' statement has the wrong module name + | DuplicateModuleName P.ModName FilePath FilePath + -- ^ Two modules loaded from different files have the same module name deriving (Show) instance PP ModuleError where @@ -106,6 +109,11 @@ instance PP ModuleError where , text "Expected:" <+> pp expected ]) + DuplicateModuleName name path1 path2 -> + hang (text "[error] module" <+> pp name <+> + text "is defined in multiple files:") + 4 (vcat [text path1, text path2]) + OtherFailure x -> text x @@ -148,6 +156,10 @@ moduleNameMismatch :: P.ModName -> Located P.ModName -> ModuleM a moduleNameMismatch expected found = ModuleT (raise (ModuleNameMismatch expected found)) +duplicateModuleName :: P.ModName -> FilePath -> FilePath -> ModuleM a +duplicateModuleName name path1 path2 = + ModuleT (raise (DuplicateModuleName name path1 path2)) + -- Warnings -------------------------------------------------------------------- @@ -298,10 +310,10 @@ setNameSeeds seeds = ModuleT $ do env <- get set $! env { meNameSeeds = seeds } -loadedModule :: T.Module -> ModuleM () -loadedModule m = ModuleT $ do +loadedModule :: FilePath -> T.Module -> ModuleM () +loadedModule path m = ModuleT $ do env <- get - set $! env { meLoadedModules = addLoadedModule m (meLoadedModules env) } + set $! env { meLoadedModules = addLoadedModule path m (meLoadedModules env) } modifyEvalEnv :: (EvalEnv -> EvalEnv) -> ModuleM () modifyEvalEnv f = ModuleT $ do