PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/share/guile/2.0/language/tree-il
Viewing File: /usr/share/guile/2.0/language/tree-il/peval.scm
;;; Tree-IL partial evaluator ;; Copyright (C) 2011-2014 Free Software Foundation, Inc. ;;;; This library is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU Lesser General Public ;;;; License as published by the Free Software Foundation; either ;;;; version 3 of the License, or (at your option) any later version. ;;;; ;;;; This library is distributed in the hope that it will be useful, ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;;; Lesser General Public License for more details. ;;;; ;;;; You should have received a copy of the GNU Lesser General Public ;;;; License along with this library; if not, write to the Free Software ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA (define-module (language tree-il peval) #:use-module (language tree-il) #:use-module (language tree-il primitives) #:use-module (language tree-il effects) #:use-module (ice-9 vlist) #:use-module (ice-9 match) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:use-module (ice-9 control) #:export (peval)) ;;; ;;; Partial evaluation is Guile's most important source-to-source ;;; optimization pass. It performs copy propagation, dead code ;;; elimination, inlining, and constant folding, all while preserving ;;; the order of effects in the residual program. ;;; ;;; For more on partial evaluation, see William Cook’s excellent ;;; tutorial on partial evaluation at DSL 2011, called “Build your own ;;; partial evaluator in 90 minutes”[0]. ;;; ;;; Our implementation of this algorithm was heavily influenced by ;;; Waddell and Dybvig's paper, "Fast and Effective Procedure Inlining", ;;; IU CS Dept. TR 484. ;;; ;;; [0] http://www.cs.utexas.edu/~wcook/tutorial/. ;;; ;; First, some helpers. ;; (define-syntax *logging* (identifier-syntax #f)) ;; For efficiency we define *logging* to inline to #f, so that the call ;; to log* gets optimized out. If you want to log, uncomment these ;; lines: ;; ;; (define %logging #f) ;; (define-syntax *logging* (identifier-syntax %logging)) ;; ;; Then you can change %logging at runtime. (define-syntax log (syntax-rules (quote) ((log 'event arg ...) (if (and *logging* (or (eq? *logging* #t) (memq 'event *logging*))) (log* 'event arg ...))))) (define (log* event . args) (let ((pp (module-ref (resolve-interface '(ice-9 pretty-print)) 'pretty-print))) (pp `(log ,event . ,args)) (newline) (values))) (define (tree-il-any proc exp) (let/ec k (tree-il-fold (lambda (exp res) (let ((res (proc exp))) (if res (k res) #f))) (lambda (exp res) (let ((res (proc exp))) (if res (k res) #f))) (lambda (exp res) #f) #f exp))) (define (vlist-any proc vlist) (let ((len (vlist-length vlist))) (let lp ((i 0)) (and (< i len) (or (proc (vlist-ref vlist i)) (lp (1+ i))))))) (define (singly-valued-expression? exp) (match exp (($ <const>) #t) (($ <lexical-ref>) #t) (($ <void>) #t) (($ <lexical-ref>) #t) (($ <primitive-ref>) #t) (($ <module-ref>) #t) (($ <toplevel-ref>) #t) (($ <application> _ ($ <primitive-ref> _ (? singly-valued-primitive?))) #t) (($ <application> _ ($ <primitive-ref> _ 'values) (val)) #t) (($ <lambda>) #t) (else #f))) (define (truncate-values x) "Discard all but the first value of X." (if (singly-valued-expression? x) x (make-application (tree-il-src x) (make-primitive-ref #f 'values) (list x)))) ;; Peval will do a one-pass analysis on the source program to determine ;; the set of assigned lexicals, and to identify unreferenced and ;; singly-referenced lexicals. ;; (define-record-type <var> (make-var name gensym refcount set?) var? (name var-name) (gensym var-gensym) (refcount var-refcount set-var-refcount!) (set? var-set? set-var-set?!)) (define* (build-var-table exp #:optional (table vlist-null)) (tree-il-fold (lambda (exp res) (match exp (($ <lexical-ref> src name gensym) (let ((var (cdr (vhash-assq gensym res)))) (set-var-refcount! var (1+ (var-refcount var))) res)) (_ res))) (lambda (exp res) (match exp (($ <lambda-case> src req opt rest kw init gensyms body alt) (fold (lambda (name sym res) (vhash-consq sym (make-var name sym 0 #f) res)) res (append req (or opt '()) (if rest (list rest) '()) (match kw ((aok? (kw name sym) ...) name) (_ '()))) gensyms)) (($ <let> src names gensyms vals body) (fold (lambda (name sym res) (vhash-consq sym (make-var name sym 0 #f) res)) res names gensyms)) (($ <letrec> src in-order? names gensyms vals body) (fold (lambda (name sym res) (vhash-consq sym (make-var name sym 0 #f) res)) res names gensyms)) (($ <fix> src names gensyms vals body) (fold (lambda (name sym res) (vhash-consq sym (make-var name sym 0 #f) res)) res names gensyms)) (($ <lexical-set> src name gensym exp) (set-var-set?! (cdr (vhash-assq gensym res)) #t) res) (_ res))) (lambda (exp res) res) table exp)) ;; Counters are data structures used to limit the effort that peval ;; spends on particular inlining attempts. Each call site in the source ;; program is allocated some amount of effort. If peval exceeds the ;; effort counter while attempting to inline a call site, it aborts the ;; inlining attempt and residualizes a call instead. ;; ;; As there is a fixed number of call sites, that makes `peval' O(N) in ;; the number of call sites in the source program. ;; ;; Counters should limit the size of the residual program as well, but ;; currently this is not implemented. ;; ;; At the top level, before seeing any peval call, there is no counter, ;; because inlining will terminate as there is no recursion. When peval ;; sees a call at the top level, it will make a new counter, allocating ;; it some amount of effort and size. ;; ;; This top-level effort counter effectively "prints money". Within a ;; toplevel counter, no more effort is printed ex nihilo; for a nested ;; inlining attempt to proceed, effort must be transferred from the ;; toplevel counter to the nested counter. ;; ;; Via `data' and `prev', counters form a linked list, terminating in a ;; toplevel counter. In practice `data' will be the a pointer to the ;; source expression of the procedure being inlined. ;; ;; In this way peval can detect a recursive inlining attempt, by walking ;; back on the `prev' links looking for matching `data'. Recursive ;; counters receive a more limited effort allocation, as we don't want ;; to spend all of the effort for a toplevel inlining site on loops. ;; Also, recursive counters don't need a prompt at each inlining site: ;; either the call chain folds entirely, or it will be residualized at ;; its original call. ;; (define-record-type <counter> (%make-counter effort size continuation recursive? data prev) counter? (effort effort-counter) (size size-counter) (continuation counter-continuation) (recursive? counter-recursive? set-counter-recursive?!) (data counter-data) (prev counter-prev)) (define (abort-counter c) ((counter-continuation c))) (define (record-effort! c) (let ((e (effort-counter c))) (if (zero? (variable-ref e)) (abort-counter c) (variable-set! e (1- (variable-ref e)))))) (define (record-size! c) (let ((s (size-counter c))) (if (zero? (variable-ref s)) (abort-counter c) (variable-set! s (1- (variable-ref s)))))) (define (find-counter data counter) (and counter (if (eq? data (counter-data counter)) counter (find-counter data (counter-prev counter))))) (define* (transfer! from to #:optional (effort (variable-ref (effort-counter from))) (size (variable-ref (size-counter from)))) (define (transfer-counter! from-v to-v amount) (let* ((from-balance (variable-ref from-v)) (to-balance (variable-ref to-v)) (amount (min amount from-balance))) (variable-set! from-v (- from-balance amount)) (variable-set! to-v (+ to-balance amount)))) (transfer-counter! (effort-counter from) (effort-counter to) effort) (transfer-counter! (size-counter from) (size-counter to) size)) (define (make-top-counter effort-limit size-limit continuation data) (%make-counter (make-variable effort-limit) (make-variable size-limit) continuation #t data #f)) (define (make-nested-counter continuation data current) (let ((c (%make-counter (make-variable 0) (make-variable 0) continuation #f data current))) (transfer! current c) c)) (define (make-recursive-counter effort-limit size-limit orig current) (let ((c (%make-counter (make-variable 0) (make-variable 0) (counter-continuation orig) #t (counter-data orig) current))) (transfer! current c effort-limit size-limit) c)) ;; Operand structures allow bindings to be processed lazily instead of ;; eagerly. By doing so, hopefully we can get process them in a way ;; appropriate to their use contexts. Operands also prevent values from ;; being visited multiple times, wasting effort. ;; ;; TODO: Record value size in operand structure? ;; (define-record-type <operand> (%make-operand var sym visit source visit-count use-count copyable? residual-value constant-value alias) operand? (var operand-var) (sym operand-sym) (visit %operand-visit) (source operand-source) (visit-count operand-visit-count set-operand-visit-count!) (use-count operand-use-count set-operand-use-count!) (copyable? operand-copyable? set-operand-copyable?!) (residual-value operand-residual-value %set-operand-residual-value!) (constant-value operand-constant-value set-operand-constant-value!) (alias operand-alias set-operand-alias!)) (define* (make-operand var sym #:optional source visit alias) ;; Bind SYM to VAR, with value SOURCE. Unassigned bound operands are ;; considered copyable until we prove otherwise. If we have a source ;; expression, truncate it to one value. Copy propagation does not ;; work on multiply-valued expressions. (let ((source (and=> source truncate-values))) (%make-operand var sym visit source 0 0 (and source (not (var-set? var))) #f #f (and (not (var-set? var)) alias)))) (define* (make-bound-operands vars syms sources visit #:optional aliases) (if aliases (map (lambda (name sym source alias) (make-operand name sym source visit alias)) vars syms sources aliases) (map (lambda (name sym source) (make-operand name sym source visit #f)) vars syms sources))) (define (make-unbound-operands vars syms) (map make-operand vars syms)) (define (set-operand-residual-value! op val) (%set-operand-residual-value! op (match val (($ <application> src ($ <primitive-ref> _ 'values) (first)) ;; The continuation of a residualized binding does not need the ;; introduced `values' node, so undo the effects of truncation. first) (else val)))) (define* (visit-operand op counter ctx #:optional effort-limit size-limit) ;; Peval is O(N) in call sites of the source program. However, ;; visiting an operand can introduce new call sites. If we visit an ;; operand outside a counter -- i.e., outside an inlining attempt -- ;; this can lead to divergence. So, if we are visiting an operand to ;; try to copy it, and there is no counter, make a new one. ;; ;; This will only happen at most as many times as there are lexical ;; references in the source program. (and (zero? (operand-visit-count op)) (dynamic-wind (lambda () (set-operand-visit-count! op (1+ (operand-visit-count op)))) (lambda () (and (operand-source op) (if (or counter (and (not effort-limit) (not size-limit))) ((%operand-visit op) (operand-source op) counter ctx) (let/ec k (define (abort) ;; If we abort when visiting the value in a ;; fresh context, we won't succeed in any future ;; attempt, so don't try to copy it again. (set-operand-copyable?! op #f) (k #f)) ((%operand-visit op) (operand-source op) (make-top-counter effort-limit size-limit abort op) ctx))))) (lambda () (set-operand-visit-count! op (1- (operand-visit-count op))))))) ;; A helper for constant folding. ;; (define (types-check? primitive-name args) (case primitive-name ((values) #t) ((not pair? null? list? symbol? vector? struct?) (= (length args) 1)) ((eq? eqv? equal?) (= (length args) 2)) ;; FIXME: add more cases? (else #f))) (define* (peval exp #:optional (cenv (current-module)) (env vlist-null) #:key (operator-size-limit 40) (operand-size-limit 20) (value-size-limit 10) (effort-limit 500) (recursive-effort-limit 100)) "Partially evaluate EXP in compilation environment CENV, with top-level bindings from ENV and return the resulting expression." ;; This is a simple partial evaluator. It effectively performs ;; constant folding, copy propagation, dead code elimination, and ;; inlining. ;; TODO: ;; ;; Propagate copies across toplevel bindings, if we can prove the ;; bindings to be immutable. ;; ;; Specialize lambda expressions with invariant arguments. (define local-toplevel-env ;; The top-level environment of the module being compiled. (match exp (($ <toplevel-define> _ name) (vhash-consq name #t env)) (($ <sequence> _ exps) (fold (lambda (x r) (match x (($ <toplevel-define> _ name) (vhash-consq name #t r)) (_ r))) env exps)) (_ env))) (define (local-toplevel? name) (vhash-assq name local-toplevel-env)) ;; gensym -> <var> ;; renamed-term -> original-term ;; (define store (build-var-table exp)) (define (record-new-temporary! name sym refcount) (set! store (vhash-consq sym (make-var name sym refcount #f) store))) (define (lookup-var sym) (let ((v (vhash-assq sym store))) (if v (cdr v) (error "unbound var" sym (vlist->list store))))) (define (fresh-gensyms vars) (map (lambda (var) (let ((new (gensym (string-append (symbol->string (var-name var)) " ")))) (set! store (vhash-consq new var store)) new)) vars)) (define (fresh-temporaries ls) (map (lambda (elt) (let ((new (gensym "tmp "))) (record-new-temporary! 'tmp new 1) new)) ls)) (define (assigned-lexical? sym) (var-set? (lookup-var sym))) (define (lexical-refcount sym) (var-refcount (lookup-var sym))) ;; ORIG has been alpha-renamed to NEW. Analyze NEW and record a link ;; from it to ORIG. ;; (define (record-source-expression! orig new) (set! store (vhash-consq new (source-expression orig) store)) new) ;; Find the source expression corresponding to NEW. Used to detect ;; recursive inlining attempts. ;; (define (source-expression new) (let ((x (vhash-assq new store))) (if x (cdr x) new))) (define (record-operand-use op) (set-operand-use-count! op (1+ (operand-use-count op)))) (define (unrecord-operand-uses op n) (let ((count (- (operand-use-count op) n))) (when (zero? count) (set-operand-residual-value! op #f)) (set-operand-use-count! op count))) (define* (residualize-lexical op #:optional ctx val) (log 'residualize op) (record-operand-use op) (if (memq ctx '(value values)) (set-operand-residual-value! op val)) (make-lexical-ref #f (var-name (operand-var op)) (operand-sym op))) (define (fold-constants src name args ctx) (define (apply-primitive name args) ;; todo: further optimize commutative primitives (catch #t (lambda () (call-with-values (lambda () (case name ((eq? eqv?) ;; Constants will be deduplicated later, but eq? ;; folding can happen now. Anticipate the ;; deduplication by using equal? instead of eq?. ;; Same for eqv?. (apply equal? args)) (else (apply (module-ref the-scm-module name) args)))) (lambda results (values #t results)))) (lambda _ (values #f '())))) (define (make-values src values) (match values ((single) single) ; 1 value ((_ ...) ; 0, or 2 or more values (make-application src (make-primitive-ref src 'values) values)))) (define (residualize-call) (make-application src (make-primitive-ref #f name) args)) (cond ((every const? args) (let-values (((success? values) (apply-primitive name (map const-exp args)))) (log 'fold success? values name args) (if success? (case ctx ((effect) (make-void src)) ((test) ;; Values truncation: only take the first ;; value. (if (pair? values) (make-const src (car values)) (make-values src '()))) (else (make-values src (map (cut make-const src <>) values)))) (residualize-call)))) ((and (eq? ctx 'effect) (types-check? name args)) (make-void #f)) (else (residualize-call)))) (define (inline-values src exp nmin nmax consumer) (let loop ((exp exp)) (match exp ;; Some expression types are always singly-valued. ((or ($ <const>) ($ <void>) ($ <lambda>) ($ <lexical-ref>) ($ <toplevel-ref>) ($ <module-ref>) ($ <primitive-ref>) ($ <dynref>) ($ <lexical-set>) ; FIXME: these set! expressions ($ <toplevel-set>) ; could return zero values in ($ <toplevel-define>) ; the future ($ <module-set>) ; ($ <dynset>) ; ($ <application> src ($ <primitive-ref> _ (? singly-valued-primitive?)))) (and (<= nmin 1) (or (not nmax) (>= nmax 1)) (make-application src (make-lambda #f '() consumer) (list exp)))) ;; Statically-known number of values. (($ <application> src ($ <primitive-ref> _ 'values) vals) (and (<= nmin (length vals)) (or (not nmax) (>= nmax (length vals))) (make-application src (make-lambda #f '() consumer) vals))) ;; Not going to copy code into both branches. (($ <conditional>) #f) ;; Bail on other applications. (($ <application>) #f) ;; Bail on prompt and abort. (($ <prompt>) #f) (($ <abort>) #f) ;; Propagate to tail positions. (($ <let> src names gensyms vals body) (let ((body (loop body))) (and body (make-let src names gensyms vals body)))) (($ <letrec> src in-order? names gensyms vals body) (let ((body (loop body))) (and body (make-letrec src in-order? names gensyms vals body)))) (($ <fix> src names gensyms vals body) (let ((body (loop body))) (and body (make-fix src names gensyms vals body)))) (($ <let-values> src exp ($ <lambda-case> src2 req opt rest kw inits gensyms body #f)) (let ((body (loop body))) (and body (make-let-values src exp (make-lambda-case src2 req opt rest kw inits gensyms body #f))))) (($ <dynwind> src winder body unwinder) (let ((body (loop body))) (and body (make-dynwind src winder body unwinder)))) (($ <dynlet> src fluids vals body) (let ((body (loop body))) (and body (make-dynlet src fluids vals body)))) (($ <sequence> src exps) (match exps ((head ... tail) (let ((tail (loop tail))) (and tail (make-sequence src (append head (list tail))))))))))) (define compute-effects (make-effects-analyzer assigned-lexical?)) (define (constant-expression? x) ;; Return true if X is constant, for the purposes of copying or ;; elision---i.e., if it is known to have no effects, does not ;; allocate storage for a mutable object, and does not access ;; mutable data (like `car' or toplevel references). (constant? (compute-effects x))) (define (prune-bindings ops in-order? body counter ctx build-result) ;; This helper handles both `let' and `letrec'/`fix'. In the latter ;; cases we need to make sure that if referenced binding A needs ;; as-yet-unreferenced binding B, that B is processed for value. ;; Likewise if C, when processed for effect, needs otherwise ;; unreferenced D, then D needs to be processed for value too. ;; (define (referenced? op) ;; When we visit lambdas in operator context, we just copy them, ;; as we will process their body later. However this does have ;; the problem that any free var referenced by the lambda is not ;; marked as needing residualization. Here we hack around this ;; and treat all bindings as referenced if we are in operator ;; context. (or (eq? ctx 'operator) (not (zero? (operand-use-count op))))) ;; values := (op ...) ;; effects := (op ...) (define (residualize values effects) ;; Note, values and effects are reversed. (cond (in-order? (let ((values (filter operand-residual-value ops))) (if (null? values) body (build-result (map (compose var-name operand-var) values) (map operand-sym values) (map operand-residual-value values) body)))) (else (let ((body (if (null? effects) body (let ((effect-vals (map operand-residual-value effects))) (make-sequence #f (reverse (cons body effect-vals))))))) (if (null? values) body (let ((values (reverse values))) (build-result (map (compose var-name operand-var) values) (map operand-sym values) (map operand-residual-value values) body))))))) ;; old := (bool ...) ;; values := (op ...) ;; effects := ((op . value) ...) (let prune ((old (map referenced? ops)) (values '()) (effects '())) (let lp ((ops* ops) (values values) (effects effects)) (cond ((null? ops*) (let ((new (map referenced? ops))) (if (not (equal? new old)) (prune new values '()) (residualize values (map (lambda (op val) (set-operand-residual-value! op val) op) (map car effects) (map cdr effects)))))) (else (let ((op (car ops*))) (cond ((memq op values) (lp (cdr ops*) values effects)) ((operand-residual-value op) (lp (cdr ops*) (cons op values) effects)) ((referenced? op) (set-operand-residual-value! op (visit-operand op counter 'value)) (lp (cdr ops*) (cons op values) effects)) (else (lp (cdr ops*) values (let ((effect (visit-operand op counter 'effect))) (if (void? effect) effects (acons op effect effects)))))))))))) (define (small-expression? x limit) (let/ec k (tree-il-fold (lambda (x res) ; leaf (1+ res)) (lambda (x res) ; down (1+ res)) (lambda (x res) ; up (if (< res limit) res (k #f))) 0 x) #t)) (define (extend-env sym op env) (vhash-consq (operand-sym op) op (vhash-consq sym op env))) (let loop ((exp exp) (env vlist-null) ; vhash of gensym -> <operand> (counter #f) ; inlined call stack (ctx 'values)) ; effect, value, values, test, operator, or call (define (lookup var) (cond ((vhash-assq var env) => cdr) (else (error "unbound var" var)))) ;; Find a value referenced a specific number of times. This is a hack ;; that's used for propagating fresh data structures like rest lists and ;; prompt tags. Usually we wouldn't copy consed data, but we can do so in ;; some special cases like `apply' or prompts if we can account ;; for all of its uses. ;; ;; You don't want to use this in general because it introduces a slight ;; nonlinearity by running peval again (though with a small effort and size ;; counter). ;; (define (find-definition x n-aliases) (cond ((lexical-ref? x) (cond ((lookup (lexical-ref-gensym x)) => (lambda (op) (if (var-set? (operand-var op)) (values #f #f) (let ((y (or (operand-residual-value op) (visit-operand op counter 'value 10 10) (operand-source op)))) (cond ((and (lexical-ref? y) (= (lexical-refcount (lexical-ref-gensym x)) 1)) ;; X is a simple alias for Y. Recurse, regardless of ;; the number of aliases we were expecting. (find-definition y n-aliases)) ((= (lexical-refcount (lexical-ref-gensym x)) n-aliases) ;; We found a definition that is aliased the right ;; number of times. We still recurse in case it is a ;; lexical. (values (find-definition y 1) op)) (else ;; We can't account for our aliases. (values #f #f))))))) (else ;; A formal parameter. Can't say anything about that. (values #f #f)))) ((= n-aliases 1) ;; Not a lexical: success, but only if we are looking for an ;; unaliased value. (values x #f)) (else (values #f #f)))) (define (visit exp ctx) (loop exp env counter ctx)) (define (for-value exp) (visit exp 'value)) (define (for-values exp) (visit exp 'values)) (define (for-test exp) (visit exp 'test)) (define (for-effect exp) (visit exp 'effect)) (define (for-call exp) (visit exp 'call)) (define (for-tail exp) (visit exp ctx)) (if counter (record-effort! counter)) (log 'visit ctx (and=> counter effort-counter) (unparse-tree-il exp)) (match exp (($ <const>) (case ctx ((effect) (make-void #f)) (else exp))) (($ <void>) (case ctx ((test) (make-const #f #t)) (else exp))) (($ <lexical-ref> _ _ gensym) (log 'begin-copy gensym) (let lp ((op (lookup gensym))) (cond ((eq? ctx 'effect) (log 'lexical-for-effect gensym) (make-void #f)) ((operand-alias op) ;; This is an unassigned operand that simply aliases some ;; other operand. Recurse to avoid residualizing the leaf ;; binding. => lp) ((eq? ctx 'call) ;; Don't propagate copies if we are residualizing a call. (log 'residualize-lexical-call gensym op) (residualize-lexical op)) ((var-set? (operand-var op)) ;; Assigned lexicals don't copy-propagate. (log 'assigned-var gensym op) (residualize-lexical op)) ((not (operand-copyable? op)) ;; We already know that this operand is not copyable. (log 'not-copyable gensym op) (residualize-lexical op)) ((and=> (operand-constant-value op) (lambda (x) (or (const? x) (void? x) (primitive-ref? x)))) ;; A cache hit. (let ((val (operand-constant-value op))) (log 'memoized-constant gensym val) (for-tail val))) ((visit-operand op counter (if (eq? ctx 'values) 'value ctx) recursive-effort-limit operand-size-limit) => ;; If we end up deciding to residualize this value instead of ;; copying it, save that residualized value. (lambda (val) (cond ((not (constant-expression? val)) (log 'not-constant gensym op) ;; At this point, ctx is operator, test, or value. A ;; value that is non-constant in one context will be ;; non-constant in the others, so it's safe to record ;; that here, and avoid future visits. (set-operand-copyable?! op #f) (residualize-lexical op ctx val)) ((or (const? val) (void? val) (primitive-ref? val)) ;; Always propagate simple values that cannot lead to ;; code bloat. (log 'copy-simple gensym val) ;; It could be this constant is the result of folding. ;; If that is the case, cache it. This helps loop ;; unrolling get farther. (if (or (eq? ctx 'value) (eq? ctx 'values)) (begin (log 'memoize-constant gensym val) (set-operand-constant-value! op val))) val) ((= 1 (var-refcount (operand-var op))) ;; Always propagate values referenced only once. (log 'copy-single gensym val) val) ;; FIXME: do demand-driven size accounting rather than ;; these heuristics. ((eq? ctx 'operator) ;; A pure expression in the operator position. Inline ;; if it's a lambda that's small enough. (if (and (lambda? val) (small-expression? val operator-size-limit)) (begin (log 'copy-operator gensym val) val) (begin (log 'too-big-for-operator gensym val) (residualize-lexical op ctx val)))) (else ;; A pure expression, processed for call or for value. ;; Don't inline lambdas, because they will probably won't ;; fold because we don't know the operator. (if (and (small-expression? val value-size-limit) (not (tree-il-any lambda? val))) (begin (log 'copy-value gensym val) val) (begin (log 'too-big-or-has-lambda gensym val) (residualize-lexical op ctx val))))))) (else ;; Visit failed. Either the operand isn't bound, as in ;; lambda formal parameters, or the copy was aborted. (log 'unbound-or-aborted gensym op) (residualize-lexical op))))) (($ <lexical-set> src name gensym exp) (let ((op (lookup gensym))) (if (zero? (var-refcount (operand-var op))) (let ((exp (for-effect exp))) (if (void? exp) exp (make-sequence src (list exp (make-void #f))))) (begin (record-operand-use op) (make-lexical-set src name (operand-sym op) (for-value exp)))))) (($ <let> src (names ... rest) (gensyms ... rest-sym) (vals ... ($ <application> _ ($ <primitive-ref> _ 'list) rest-args)) ($ <application> asrc ($ <primitive-ref> _ (or 'apply '@apply)) (proc args ... ($ <lexical-ref> _ (? (cut eq? <> rest)) (? (lambda (sym) (and (eq? sym rest-sym) (= (lexical-refcount sym) 1)))))))) (let* ((tmps (make-list (length rest-args) 'tmp)) (tmp-syms (fresh-temporaries tmps))) (for-tail (make-let src (append names tmps) (append gensyms tmp-syms) (append vals rest-args) (make-application asrc proc (append args (map (cut make-lexical-ref #f <> <>) tmps tmp-syms))))))) (($ <let> src names gensyms vals body) (define (lookup-alias exp) ;; It's very common for macros to introduce something like: ;; ;; ((lambda (x y) ...) x-exp y-exp) ;; ;; In that case you might end up trying to inline something like: ;; ;; (let ((x x-exp) (y y-exp)) ...) ;; ;; But if x-exp is itself a lexical-ref that aliases some much ;; larger expression, perhaps it will fail to inline due to ;; size. However we don't want to introduce a useless alias ;; (in this case, x). So if the RHS of a let expression is a ;; lexical-ref, we record that expression. If we end up having ;; to residualize X, then instead we residualize X-EXP, as long ;; as it isn't assigned. ;; (match exp (($ <lexical-ref> _ _ sym) (let ((op (lookup sym))) (and (not (var-set? (operand-var op))) op))) (_ #f))) (let* ((vars (map lookup-var gensyms)) (new (fresh-gensyms vars)) (ops (make-bound-operands vars new vals (lambda (exp counter ctx) (loop exp env counter ctx)) (map lookup-alias vals))) (env (fold extend-env env gensyms ops)) (body (loop body env counter ctx))) (cond ((const? body) (for-tail (make-sequence src (append vals (list body))))) ((and (lexical-ref? body) (memq (lexical-ref-gensym body) new)) (let ((sym (lexical-ref-gensym body)) (pairs (map cons new vals))) ;; (let ((x foo) (y bar) ...) x) => (begin bar ... foo) (for-tail (make-sequence src (append (map cdr (alist-delete sym pairs eq?)) (list (assq-ref pairs sym))))))) (else ;; Only include bindings for which lexical references ;; have been residualized. (prune-bindings ops #f body counter ctx (lambda (names gensyms vals body) (if (null? names) (error "what!" names)) (make-let src names gensyms vals body))))))) (($ <letrec> src in-order? names gensyms vals body) ;; Note the difference from the `let' case: here we use letrec* ;; so that the `visit' procedure for the new operands closes over ;; an environment that includes the operands. Also we don't try ;; to elide aliases, because we can't sensibly reduce something ;; like (letrec ((a b) (b a)) a). (letrec* ((visit (lambda (exp counter ctx) (loop exp env* counter ctx))) (vars (map lookup-var gensyms)) (new (fresh-gensyms vars)) (ops (make-bound-operands vars new vals visit)) (env* (fold extend-env env gensyms ops)) (body* (visit body counter ctx))) (if (and (const? body*) (every constant-expression? vals)) ;; We may have folded a loop completely, even though there ;; might be cyclical references between the bound values. ;; Handle this degenerate case specially. body* (prune-bindings ops in-order? body* counter ctx (lambda (names gensyms vals body) (make-letrec src in-order? names gensyms vals body)))))) (($ <fix> src names gensyms vals body) (letrec* ((visit (lambda (exp counter ctx) (loop exp env* counter ctx))) (vars (map lookup-var gensyms)) (new (fresh-gensyms vars)) (ops (make-bound-operands vars new vals visit)) (env* (fold extend-env env gensyms ops)) (body* (visit body counter ctx))) (if (const? body*) body* (prune-bindings ops #f body* counter ctx (lambda (names gensyms vals body) (make-fix src names gensyms vals body)))))) (($ <let-values> lv-src producer consumer) ;; Peval the producer, then try to inline the consumer into ;; the producer. If that succeeds, peval again. Otherwise ;; reconstruct the let-values, pevaling the consumer. (let ((producer (for-values producer))) (or (match consumer (($ <lambda-case> src req opt rest #f inits gensyms body #f) (let* ((nmin (length req)) (nmax (and (not rest) (+ nmin (if opt (length opt) 0))))) (cond ((inline-values lv-src producer nmin nmax consumer) => for-tail) (else #f)))) (_ #f)) (make-let-values lv-src producer (for-tail consumer))))) (($ <dynwind> src winder body unwinder) (let ((pre (for-value winder)) (body (for-tail body)) (post (for-value unwinder))) (cond ((not (constant-expression? pre)) (cond ((not (constant-expression? post)) (let ((pre-sym (gensym "pre-")) (post-sym (gensym "post-"))) (record-new-temporary! 'pre pre-sym 1) (record-new-temporary! 'post post-sym 1) (make-let src '(pre post) (list pre-sym post-sym) (list pre post) (make-dynwind src (make-lexical-ref #f 'pre pre-sym) body (make-lexical-ref #f 'post post-sym))))) (else (let ((pre-sym (gensym "pre-"))) (record-new-temporary! 'pre pre-sym 1) (make-let src '(pre) (list pre-sym) (list pre) (make-dynwind src (make-lexical-ref #f 'pre pre-sym) body post)))))) ((not (constant-expression? post)) (let ((post-sym (gensym "post-"))) (record-new-temporary! 'post post-sym 1) (make-let src '(post) (list post-sym) (list post) (make-dynwind src pre body (make-lexical-ref #f 'post post-sym))))) (else (make-dynwind src pre body post))))) (($ <dynlet> src fluids vals body) (make-dynlet src (map for-value fluids) (map for-value vals) (for-tail body))) (($ <dynref> src fluid) (make-dynref src (for-value fluid))) (($ <dynset> src fluid exp) (make-dynset src (for-value fluid) (for-value exp))) (($ <toplevel-ref> src (? effect-free-primitive? name)) (if (local-toplevel? name) exp (let ((exp (resolve-primitives! exp cenv))) (if (primitive-ref? exp) (for-tail exp) exp)))) (($ <toplevel-ref>) ;; todo: open private local bindings. exp) (($ <module-ref> src module (? effect-free-primitive? name) #f) (let ((module (false-if-exception (resolve-module module #:ensure #f)))) (if (module? module) (let ((var (module-variable module name))) (if (eq? var (module-variable the-scm-module name)) (make-primitive-ref src name) exp)) exp))) (($ <module-ref>) exp) (($ <module-set> src mod name public? exp) (make-module-set src mod name public? (for-value exp))) (($ <toplevel-define> src name exp) (make-toplevel-define src name (for-value exp))) (($ <toplevel-set> src name exp) (make-toplevel-set src name (for-value exp))) (($ <primitive-ref>) (case ctx ((effect) (make-void #f)) ((test) (make-const #f #t)) (else exp))) (($ <conditional> src condition subsequent alternate) (define (call-with-failure-thunk exp proc) (match exp (($ <application> _ _ ()) (proc exp)) (($ <const>) (proc exp)) (($ <void>) (proc exp)) (($ <lexical-ref>) (proc exp)) (_ (let ((t (gensym "failure-"))) (record-new-temporary! 'failure t 2) (make-let src (list 'failure) (list t) (list (make-lambda #f '() (make-lambda-case #f '() #f #f #f '() '() exp #f))) (proc (make-application #f (make-lexical-ref #f 'failure t) '()))))))) (define (simplify-conditional c) (match c ;; Swap the arms of (if (not FOO) A B), to simplify. (($ <conditional> src ($ <application> _ ($ <primitive-ref> _ 'not) (pred)) subsequent alternate) (simplify-conditional (make-conditional src pred alternate subsequent))) ;; Special cases for common tests in the predicates of chains ;; of if expressions. (($ <conditional> src ($ <conditional> src* outer-test inner-test ($ <const> _ #f)) inner-subsequent alternate) (let lp ((alternate alternate)) (match alternate ;; Lift a common repeated test out of a chain of if ;; expressions. (($ <conditional> _ (? (cut tree-il=? outer-test <>)) other-subsequent alternate) (make-conditional src outer-test (simplify-conditional (make-conditional src* inner-test inner-subsequent other-subsequent)) alternate)) ;; Likewise, but punching through any surrounding ;; failure continuations. (($ <let> let-src (name) (sym) ((and thunk ($ <lambda>))) body) (make-let let-src (list name) (list sym) (list thunk) (lp body))) ;; Otherwise, rotate AND tests to expose a simple ;; condition in the front. Although this may result in ;; lexically binding failure thunks, the thunks will be ;; compiled to labels allocation, so there's no actual ;; code growth. (_ (call-with-failure-thunk alternate (lambda (failure) (make-conditional src outer-test (simplify-conditional (make-conditional src* inner-test inner-subsequent failure)) failure))))))) (_ c))) (match (for-test condition) (($ <const> _ val) (if val (for-tail subsequent) (for-tail alternate))) (c (simplify-conditional (make-conditional src c (for-tail subsequent) (for-tail alternate)))))) (($ <application> src ($ <primitive-ref> _ '@call-with-values) (producer ($ <lambda> _ _ (and consumer ;; No optional or kwargs. ($ <lambda-case> _ req #f rest #f () gensyms body #f))))) (for-tail (make-let-values src (make-application src producer '()) consumer))) (($ <application> src ($ <primitive-ref> _ 'values) exps) (cond ((null? exps) (if (eq? ctx 'effect) (make-void #f) exp)) (else (let ((vals (map for-value exps))) (if (and (case ctx ((value test effect) #t) (else (null? (cdr vals)))) (every singly-valued-expression? vals)) (for-tail (make-sequence src (append (cdr vals) (list (car vals))))) (make-application src (make-primitive-ref #f 'values) vals)))))) (($ <application> src (and apply ($ <primitive-ref> _ (or 'apply '@apply))) (proc args ... tail)) (let lp ((tail* (find-definition tail 1)) (speculative? #t)) (define (copyable? x) ;; Inlining a result from find-definition effectively copies it, ;; relying on the let-pruning to remove its original binding. We ;; shouldn't copy non-constant expressions. (or (not speculative?) (constant-expression? x))) (match tail* (($ <const> _ (args* ...)) (let ((args* (map (cut make-const #f <>) args*))) (for-tail (make-application src proc (append args args*))))) (($ <application> _ ($ <primitive-ref> _ 'cons) ((and head (? copyable?)) (and tail (? copyable?)))) (for-tail (make-application src apply (cons proc (append args (list head tail)))))) (($ <application> _ ($ <primitive-ref> _ 'list) (and args* ((? copyable?) ...))) (for-tail (make-application src proc (append args args*)))) (tail* (if speculative? (lp (for-value tail) #f) (let ((args (append (map for-value args) (list tail*)))) (make-application src apply (cons (for-value proc) args)))))))) (($ <application> src orig-proc orig-args) ;; todo: augment the global env with specialized functions (let revisit-proc ((proc (visit orig-proc 'operator))) (match proc (($ <primitive-ref> _ (? constructor-primitive? name)) (cond ((and (memq ctx '(effect test)) (match (cons name orig-args) ((or ('cons _ _) ('list . _) ('vector . _) ('make-prompt-tag) ('make-prompt-tag ($ <const> _ (? string?)))) #t) (_ #f))) ;; Some expressions can be folded without visiting the ;; arguments for value. (let ((res (if (eq? ctx 'effect) (make-void #f) (make-const #f #t)))) (for-tail (make-sequence src (append orig-args (list res)))))) (else (match (cons name (map for-value orig-args)) (('cons head tail) (match tail (($ <const> src (? (cut eq? <> '()))) (make-application src (make-primitive-ref #f 'list) (list head))) (($ <application> src ($ <primitive-ref> _ 'list) elts) (make-application src (make-primitive-ref #f 'list) (cons head elts))) (_ (make-application src proc (list head tail))))) ((_ . args) (make-application src proc args)))))) (($ <primitive-ref> _ (? accessor-primitive? name)) (match (cons name (map for-value orig-args)) ;; FIXME: these for-tail recursions could take place outside ;; an effort counter. (('car ($ <application> src ($ <primitive-ref> _ 'cons) (head tail))) (for-tail (make-sequence src (list tail head)))) (('cdr ($ <application> src ($ <primitive-ref> _ 'cons) (head tail))) (for-tail (make-sequence src (list head tail)))) (('car ($ <application> src ($ <primitive-ref> _ 'list) (head . tail))) (for-tail (make-sequence src (append tail (list head))))) (('cdr ($ <application> src ($ <primitive-ref> _ 'list) (head . tail))) (for-tail (make-sequence src (list head (make-application src (make-primitive-ref #f 'list) tail))))) (('car ($ <const> src (head . tail))) (for-tail (make-const src head))) (('cdr ($ <const> src (head . tail))) (for-tail (make-const src tail))) (((or 'memq 'memv) k ($ <const> _ (elts ...))) ;; FIXME: factor (case ctx ((effect) (for-tail (make-sequence src (list k (make-void #f))))) ((test) (cond ((const? k) ;; A shortcut. The `else' case would handle it, but ;; this way is faster. (let ((member (case name ((memq) memq) ((memv) memv)))) (make-const #f (and (member (const-exp k) elts) #t)))) ((null? elts) (for-tail (make-sequence src (list k (make-const #f #f))))) (else (let ((t (gensym "t-")) (eq (if (eq? name 'memq) 'eq? 'eqv?))) (record-new-temporary! 't t (length elts)) (for-tail (make-let src (list 't) (list t) (list k) (let lp ((elts elts)) (define test (make-application #f (make-primitive-ref #f eq) (list (make-lexical-ref #f 't t) (make-const #f (car elts))))) (if (null? (cdr elts)) test (make-conditional src test (make-const #f #t) (lp (cdr elts))))))))))) (else (cond ((const? k) (let ((member (case name ((memq) memq) ((memv) memv)))) (make-const #f (member (const-exp k) elts)))) ((null? elts) (for-tail (make-sequence src (list k (make-const #f #f))))) (else (make-application src proc (list k (make-const #f elts)))))))) ((_ . args) (or (fold-constants src name args ctx) (make-application src proc args))))) (($ <primitive-ref> _ (? effect-free-primitive? name)) (let ((args (map for-value orig-args))) (or (fold-constants src name args ctx) (make-application src proc args)))) (($ <lambda> _ _ ($ <lambda-case> _ req opt rest #f inits gensyms body #f)) ;; Simple case: no keyword arguments. ;; todo: handle the more complex cases (let* ((nargs (length orig-args)) (nreq (length req)) (nopt (if opt (length opt) 0)) (key (source-expression proc))) (define (inlined-application) (cond ((= nargs (+ nreq nopt)) (make-let src (append req (or opt '()) (if rest (list rest) '())) gensyms (append orig-args (if rest (list (make-const #f '())) '())) body)) ((> nargs (+ nreq nopt)) (make-let src (append req (or opt '()) (list rest)) gensyms (append (take orig-args (+ nreq nopt)) (list (make-application #f (make-primitive-ref #f 'list) (drop orig-args (+ nreq nopt))))) body)) (else ;; Here we handle the case where nargs < nreq + nopt, ;; so the rest argument (if any) will be empty, and ;; there will be optional arguments that rely on their ;; default initializers. ;; ;; The default initializers of optional arguments ;; may refer to earlier arguments, so in the general ;; case we must expand into a series of nested let ;; expressions. ;; ;; In the generated code, the outermost let ;; expression will bind all arguments provided by ;; the application's argument list, as well as the ;; empty rest argument, if any. Each remaining ;; optional argument that relies on its default ;; initializer will be bound within an inner let. ;; ;; rest-gensyms, rest-vars and rest-inits will have ;; either 0 or 1 elements. They are oddly named, but ;; allow simpler code below. (let*-values (((non-rest-gensyms rest-gensyms) (split-at gensyms (+ nreq nopt))) ((provided-gensyms default-gensyms) (split-at non-rest-gensyms nargs)) ((provided-vars default-vars) (split-at (append req opt) nargs)) ((rest-vars) (if rest (list rest) '())) ((rest-inits) (if rest (list (make-const #f '())) '())) ((default-inits) (drop inits (- nargs nreq)))) (make-let src (append provided-vars rest-vars) (append provided-gensyms rest-gensyms) (append orig-args rest-inits) (fold-right (lambda (var gensym init body) (make-let src (list var) (list gensym) (list init) body)) body default-vars default-gensyms default-inits)))))) (cond ((or (< nargs nreq) (and (not rest) (> nargs (+ nreq nopt)))) ;; An error, or effecting arguments. (make-application src (for-call orig-proc) (map for-value orig-args))) ((or (and=> (find-counter key counter) counter-recursive?) (lambda? orig-proc)) ;; A recursive call, or a lambda in the operator ;; position of the source expression. Process again in ;; tail context. ;; ;; In the recursive case, mark intervening counters as ;; recursive, so we can handle a toplevel counter that ;; recurses mutually with some other procedure. ;; Otherwise, the next time we see the other procedure, ;; the effort limit would be clamped to 100. ;; (let ((found (find-counter key counter))) (if (and found (counter-recursive? found)) (let lp ((counter counter)) (if (not (eq? counter found)) (begin (set-counter-recursive?! counter #t) (lp (counter-prev counter))))))) (log 'inline-recurse key) (loop (inlined-application) env counter ctx)) (else ;; An integration at the top-level, the first ;; recursion of a recursive procedure, or a nested ;; integration of a procedure that hasn't been seen ;; yet. (log 'inline-begin exp) (let/ec k (define (abort) (log 'inline-abort exp) (k (make-application src (for-call orig-proc) (map for-value orig-args)))) (define new-counter (cond ;; These first two cases will transfer effort ;; from the current counter into the new ;; counter. ((find-counter key counter) => (lambda (prev) (make-recursive-counter recursive-effort-limit operand-size-limit prev counter))) (counter (make-nested-counter abort key counter)) ;; This case opens a new account, effectively ;; printing money. It should only do so once ;; for each call site in the source program. (else (make-top-counter effort-limit operand-size-limit abort key)))) (define result (loop (inlined-application) env new-counter ctx)) (if counter ;; The nested inlining attempt succeeded. ;; Deposit the unspent effort and size back ;; into the current counter. (transfer! new-counter counter)) (log 'inline-end result exp) result))))) (($ <let> _ _ _ vals _) ;; Attempt to inline `let' in the operator position. ;; ;; We have to re-visit the proc in value mode, since the ;; `let' bindings might have been introduced or renamed, ;; whereas the lambda (if any) in operator position has not ;; been renamed. (if (or (and-map constant-expression? vals) (and-map constant-expression? orig-args)) ;; The arguments and the let-bound values commute. (match (for-value orig-proc) (($ <let> lsrc names syms vals body) (log 'inline-let orig-proc) (for-tail (make-let lsrc names syms vals (make-application src body orig-args)))) ;; It's possible for a `let' to go away after the ;; visit due to the fact that visiting a procedure in ;; value context will prune unused bindings, whereas ;; visiting in operator mode can't because it doesn't ;; traverse through lambdas. In that case re-visit ;; the procedure. (proc (revisit-proc proc))) (make-application src (for-call orig-proc) (map for-value orig-args)))) (_ (make-application src (for-call orig-proc) (map for-value orig-args)))))) (($ <lambda> src meta body) (case ctx ((effect) (make-void #f)) ((test) (make-const #f #t)) ((operator) exp) (else (record-source-expression! exp (make-lambda src meta (and body (for-values body))))))) (($ <lambda-case> src req opt rest kw inits gensyms body alt) (define (lift-applied-lambda body gensyms) (and (not opt) rest (not kw) (match body (($ <application> _ ($ <primitive-ref> _ '@apply) (($ <lambda> _ _ (and lcase ($ <lambda-case>))) ($ <lexical-ref> _ _ sym) ...)) (and (equal? sym gensyms) (not (lambda-case-alternate lcase)) lcase)) (_ #f)))) (let* ((vars (map lookup-var gensyms)) (new (fresh-gensyms vars)) (env (fold extend-env env gensyms (make-unbound-operands vars new))) (new-sym (lambda (old) (operand-sym (cdr (vhash-assq old env))))) (body (loop body env counter ctx))) (or ;; (lambda args (apply (lambda ...) args)) => (lambda ...) (lift-applied-lambda body new) (make-lambda-case src req opt rest (match kw ((aok? (kw name old) ...) (cons aok? (map list kw name (map new-sym old)))) (_ #f)) (map (cut loop <> env counter 'value) inits) new body (and alt (for-tail alt)))))) (($ <sequence> src exps) (let lp ((exps exps) (effects '())) (match exps ((last) (if (null? effects) (for-tail last) (make-sequence src (reverse (cons (for-tail last) effects))))) ((head . rest) (let ((head (for-effect head))) (cond ((sequence? head) (lp (append (sequence-exps head) rest) effects)) ((void? head) (lp rest effects)) (else (lp rest (cons head effects))))))))) (($ <prompt> src tag body handler) (define (make-prompt-tag? x) (match x (($ <application> _ ($ <primitive-ref> _ 'make-prompt-tag) (or () ((? constant-expression?)))) #t) (_ #f))) (let ((tag (for-value tag)) (body (for-values body))) (cond ((find-definition tag 1) (lambda (val op) (make-prompt-tag? val)) => (lambda (val op) ;; There is no way that an <abort> could know the tag ;; for this <prompt>, so we can elide the <prompt> ;; entirely. (unrecord-operand-uses op 1) body)) ((find-definition tag 2) (lambda (val op) (and (make-prompt-tag? val) (abort? body) (tree-il=? (abort-tag body) tag))) => (lambda (val op) ;; (let ((t (make-prompt-tag))) ;; (call-with-prompt t ;; (lambda () (abort-to-prompt t val ...)) ;; (lambda (k arg ...) e ...))) ;; => (let-values (((k arg ...) (values values val ...))) ;; e ...) (unrecord-operand-uses op 2) (for-tail (make-let-values src (make-application #f (make-primitive-ref #f 'apply) `(,(make-primitive-ref #f 'values) ,(make-primitive-ref #f 'values) ,@(abort-args body) ,(abort-tail body))) (for-tail handler))))) (else (make-prompt src tag body (for-tail handler)))))) (($ <abort> src tag args tail) (make-abort src (for-value tag) (map for-value args) (for-value tail))))))