GHC(Glasgow Haskell Compiler)と仲良くなろう

概要

GHCの生成するコードを理解しようという話

と、見せかけて、ただ単にこれを読んだ小学生の感想文なので、元の論文が読める人には必要無い

Core言語とSTG言語

GHCは中では "元 -> Core言語 -> Stg言語 -> なんか -> なんか " という構造になっていることは、どっかで読んだことあるかと思う

Core言語、STG言語は、それぞれ、言語になってて、Haskell言語はそれに変換されたあと、最適化されたりして、Cコードになる。

簡単に説明しておくと、Core言語が、型クラスとか、ややこしいCase式とかを取り除いたもの。STG言語が…(ただいま調査中あとで書く)

Core言語

Core言語というのは、Haskell言語からややこしいSyntax Sugarを取り除いたものだと考えればよいと思う。GHCの中では、"Haskell言語 → Core言語" の変換の名前が"Desugar"になってるとか。(compiler/deSugar/Desugar.lhs:deSugarExpr)。ちなみに、HsExprがHaskellの式で、LHsExprが場所付き式。 CoreExprがCore言語の式(という解釈であってるはず)

例えば、↓こんなコードがあったとしたら、

c:: Eq a=> [a]->a->Int
c x k = case x of
          [] -> 3
          (xs:_) | k == xs -> 4
                 | otherwise -> 9

Core言語には、Boolによる条件判断、if式というのが存在していないので、ガードの部分は表現できない。 こういうのは全部、Case式に変換される

-ddump-simplオプション付きでコンパイルすると、途中のCore言語が見れるので、それで上のがどう変換されたか確認すると、

[GlobalId]
[Arity 3
 NoCafRefs]
Nanika.c =
  \ (@ a_ajB)
    ($dEq_ajH :: {GHC.Base.Eq a_ajB})
    (x_aj8 :: [a_ajB])
    (k_aj9 :: a_ajB) ->
    case x_aj8 of wild_B1 {
      [] -> GHC.Base.I# 3;
      : xs_ajc ds_dpC ->
	case $dEq_ajH of tpl_Xh { GHC.Base.:DEq tpl1_B2 tpl2_B3 ->
	case tpl1_B2 k_aj9 xs_ajc of wild1_Xs {  -- ここらへん --
	  GHC.Base.False -> GHC.Base.I# 9; GHC.Base.True -> GHC.Base.I# 4
	}
	}
    }

こんな感じ。ガードがcase式になっとる。

…と、思ったけど、それ以前にCore言語が読めないと思った。

まず、最初の GlobalId…ちょっとわからん。

Arity…引数の数? NoCafRefs 外の変数を参照してるかどうか?

んで、次。@で始まってるのが、多相型の。んで、引数が並んで "->"で関数。

まあそういうのはいいか。とにかく、Core言語というのは、ほんと原始的な関数言語という理解でよいと思う。(compiler/coreSyn/CoreSyn.lhsらへん参考)

↓こういうのを-ddump-simplしてみると良いと思う

d::Int->Int->Int
d 3 _ = 4
d _ _ = 9

ちなみに、GHCの中間表現を見るときは、数字リテラルに気を付けたほうがいい。 数字リえテラルは Num の値になって、なんか Int とは違う動きになるので、Intを明示するようにしましょう。

add x = 3+ x
addI x = (3::Int)+ (x::Int)
Nanika.add =
  \ (@ t_apf) ($dNum_apk :: {GHC.Num.Num t_apf}) ->
    let {
      lit_apg :: t_apf
      []
      lit_apg =
	case $dNum_apk
	of tpl_Xp { GHC.Num.:DNum tpl1_B2 tpl2_B3 tpl3_B4 tpl4_B5 tpl5_B6 tpl6_B7 tpl7_B8 tpl8_B9 tpl9_Ba ->
	tpl9_Ba (GHC.Num.S# 3)
	}
    } in 
      \ (x_ajz :: t_apf) ->
	case $dNum_apk
	of tpl_Xn { GHC.Num.:DNum tpl1_B2 tpl2_B3 tpl3_B4 tpl4_B5 tpl5_B6 tpl6_B7 tpl7_B8 tpl8_B9 tpl9_Ba ->
	tpl3_B4 lit_apg x_ajz
	}

Nanika.addI =
  \ (x_ajB :: GHC.Base.Int) ->
    case GHC.Num.$f6
    of tpl_XC { GHC.Num.:DNum tpl1_B2 tpl2_B3 tpl3_B4 tpl4_B5 tpl5_B6 tpl6_B7 tpl7_B8 tpl8_B9 tpl9_Ba ->
    tpl3_B4 (GHC.Base.I# 3) x_ajB
    }

こんなに違う!

STG言語