STG
頑張れば、GHCの吐くCも読めそうな気がしてきた。が、どっちかというと、C--コードを読んだ方がよい気がする。アセンブリもまあ、STGマシンのレジスタと実機のレジスタの対応関係が分かれば読めそうな気がする(対応関係とかは、include/Regs.hあたりみれば分かるかも)
{-# OPTIONS -fglasgow-exts #-} x::Double = 2.0 y::Double = 3.1 test = (sin x)+y main = return test
x1_r1H0 = NO_CCS GHC.Float.D#! [2.0]; SRT(x1_r1H0): [] y1_r1H2 = NO_CCS GHC.Float.D#! [3.1]; SRT(y1_r1H2): [] test1_r1H4 = \u srt:(0,*bitmap*) [] case GHC.Float.$fFloatingDouble of tpl_s1If { GHC.Float.:DFloating tpl1_s1Hs tpl2_s1Ig tpl3_s1Ih tpl4_s1Ii tpl5_s1Ij tpl6_s1Ik tpl7_s1Il tpl8_s1Im tpl9_s1In tpl10_s1Io tpl11_s1Ip tpl12_s1Iq tpl13_s1Ir tpl14_s1Is tpl15_s1It tpl16_s1Iu tpl17_s1Iv tpl18_s1Iw tpl19_s1Ix -> case tpl1_s1Hs of tpl20_s1Iy { GHC.Real.:DFractional tpl21_s1Hy tpl22_s1Iz tpl23_s1IA tpl24_s1I B -> case tpl21_s1Hy of tpl25_s1IC { GHC.Num.:DNum tpl26_s1ID tpl27_s1IE tpl28_s1I6 tpl29_s1IF tpl30_s1IG tpl31_s1IH tpl32_s1II tpl33_s1IJ tpl34_s1IK -> let { sat_s1I5 = \u srt:(0,*bitmap*) [] case GHC.Float.$fFloatingDouble of tpl35_s1I L { GHC.Float.:DFloating tpl36_s1IM tpl37_s1IN tpl38_s1IO tpl39_s1IP tpl40_s1IQ tpl41_s1IR tpl42_s1IS tpl43_s1I3 tpl44_s1IT tpl45_s1IU tpl46_s1IV tpl47_s1IW tpl48_s1IX tpl49_s1IY tpl50_s1IZ tpl51_s1J0 tpl52_s1J1 tpl53_s1J2 tpl54_s1J3 -> tpl43_s1I3 x1_r1H0; }; } in tpl28_s1I6 sat_s1I5 y1_r1H2; }; }; }; SRT(test1_r1H4): [GHC.Float.$fFloatingDouble]
mainは省略。やたら長いけど、Core言語と一緒でやってることは、関数テーブルをたどってるだけだったりする。
最初の"GHC.Float.:DFloating tpl1_s1Hs..."の部分は、:info Floatingすると分かるけど、Floatingクラスの関数テーブルが、
DWORD *Floating_ftbl[]={Fractional_ftbl , pi_closure , exp_closure , sqrt_closure , ...}
みたいになってる(のだと思う)。で、Fractional_ftblをたぐると、こいつは、 tpl21_s1Hy tpl22_s1Iz tpl23_s1IA tpl24_s1Iという4つのメンバーを持つ。順に、Num_ftbl,(/),recip,fromRationalに対応。で、更に、Num_ftblをたどると、3つ目に(+)演算子がメンバーに見つかる。同様に、Floatingクラスの8番目のメンバーがsinなので、tpl43_s1I3はsinを表す。
というわけで、上のコードは簡単に書くと、Core言語の段階ではまだ
test = (sin x)+y
となっているのを
let sat_s1I5 = sin x1_r1H0 in sat_s1I5 + y1_r1H2
な感じにしてるんだと思う。CPSとかANFとかと似たような感じ?と思ってたら、shelarcyさんに教えていただいたリンクに、おもっきりA Normal Formとか書いてあるので、A正規化の結果だと思います、ええ
section "data" { r1H0_closure: const GHCziFloat_Dzh_static_info; const 2.0 :: F64; } ------------------- section "data" { r1H2_closure: const GHCziFloat_Dzh_static_info; const 3.1 :: F64; } ------------------- section "relreadonly" { r1H4_srt: const GHCziFloat_zdfFloatingDouble_closure; } section "data" { r1H4_closure: const r1H4_info; const 0; const 0; const 0; } s1IL_ret() { s1IL_info { const 0; const 36; } c1Jq: I32[Sp + 0] = r1H0_closure; //=x=2.0 R1 = I32[R1 + 32]; //sin関数 Sp = Sp + (-4); jump stg_ap_p_info; } s1I5_entry() { s1I5_info { const r1H4_srt-s1I5_info; const 131072; const 65554; } c1Jt: if (Sp - 16 < SpLim) goto c1Jv; I32[Sp + (-8)] = stg_upd_frame_info; I32[Sp + (-4)] = R1; R1 = GHCziFloat_zdfFloatingDouble_closure; I32[Sp + (-12)] = s1IL_info; Sp = Sp + (-12); jump I32[R1]; c1Jv: jump stg_gc_enter_1; } s1IC_ret() { s1IC_info { const r1H4_srt-s1IC_info; const 0; const 65572; } c1Jx: Hp = Hp + 12; if (Hp > HpLim) goto c1Jz; I32[Hp + (-8)] = s1I5_info; I32[Sp + 0] = r1H2_closure; //=y=3.1 I32[Sp + (-4)] = Hp + (-8); //push sin(2.0)? R1 = I32[R1 + 12]; //(+)関数 Sp = Sp + (-8); jump stg_ap_pp_info; c1Jz: HpAlloc = 12; jump stg_gc_enter_1; } s1Iy_ret() { s1Iy_info { const r1H4_srt-s1Iy_info; const 0; const 65572; } c1JB: R1 = I32[R1 + 4]; //Num I32[Sp + 0] = s1IC_info; jump I32[R1]; } s1If_ret() { s1If_info { const r1H4_srt-s1If_info; const 0; const 65572; } c1JD: R1 = I32[R1 + 4]; //Fractional I32[Sp + 0] = s1Iy_info; jump I32[R1]; } r1H4_entry() { r1H4_info { const r1H4_srt-r1H4_info; const 131072; const 65560; } c1JG: if (Sp - 20 < SpLim) goto c1JI; Hp = Hp + 12; if (Hp > HpLim) goto c1JI; I32[Hp + (-8)] = stg_CAF_BLACKHOLE_info; call "ccall" newCAF((R1, PtrHint)); I32[R1 + 4] = Hp + (-8); I32[R1] = stg_IND_STATIC_info; I32[Sp + (-8)] = stg_upd_frame_info; I32[Sp + (-4)] = Hp + (-8); R1 = GHCziFloat_zdfFloatingDouble_closure; I32[Sp + (-12)] = s1If_info; Sp = Sp + (-12); jump I32[R1]; c1JI: HpAlloc = 12; jump stg_gc_enter_1; }
となる。はい、読めません!いやまあ、なんかSTGマシンが分かれば、これくらいはスラスラ書けるのかもしれない(書かなくていいですが)。上の関数テーブルたどって、関数呼び出してる処理をフラットにしただけだとか、そういう雰囲気くらいは掴めるよ。R1とかは、見ての通りレジスター。Spは多分スタックポインタ。131072とか謎のマジックナンバーは何だろう。Core言語->STG言語->STGマシンコードあたりの変換は、普通の関数型言語のコンパイルで使う技術と似たようなもんなのかな。にしても、各型クラスの関数テーブルの先頭に、親クラスの関数テーブルへのポインタを持っているような感じっぽいけど、いちいちこれ辿るの無駄じゃないのかなー。
というわけで、最初は、どっから手を付けていいか分からなかったGHCだけど、何とか理解できそうな気がしてきた。というか、やってることは意外と普通っぽいな〜。あと、デバッグも普通にできるんじゃね?という勇気が