OCamlのlambda IRをいじる(変数)
前回に続いて、モジュール内で変数を定義して使うプログラムがどのようなlambda IRにコンパイルされるかを見ていく。
ローカル変数1つ
OCamlで変数を定義する場合、モジュールのトップレベルでの定義と、ある式の一部としてlet x = y in z
のx
のような定義の二種類がある。
まずは後者から見ていく。
OCamlコード:
let () = let x = "hi" in print_endline x
コンパイルされたlambda IR:
(seq (let (*match*/82 = (let (x/80 = "hi") (apply (field 45 (global Stdlib!)) x/80))) 0a) 0a)
前回
(let (.. = .. ...) X)という形のXの部分で「変数を束縛した環境で評価するべき式」が入る
と書いた。(let (x/80 = "hi") (apply (field 45 (global Stdlib!)) x/80))
がまさにそれで(apply (field 45 (global Stdlib!)) x/80)
がXにあたる。
x/80
という変数を"hi"という文字列に束縛した上でStdlib
モジュールのインデックス45であるprint_endline
関数をそのx/80
変数の内容に適用している。
ローカル変数複数
複数のローカル変数を定義する場合:
let () = let x = "hi" in let y = "ho" in let z = x ^ y in print_endline z
(seq (let (*match*/84 = (let (x/80 = "hi" y/81 = "ho" z/82 = (apply (field 27 (global Stdlib!)) x/80 y/81)) (apply (field 45 (global Stdlib!)) z/82))) 0a) 0a)
OCamlだと別のlet
を連続して書いていく形になるが、lambda IRでは単一の(let ...)
に中で多重変数束縛が行われる。また、変数名のユニーク化のための数字の割り当てのロジック(新しい変数に割り振るごとに+1されていく)がはっきりわかりやすい。
ローカル変数が整数
ローカル変数が整数の場合、少しだけlambda表現が異なる。
let () = let x = 1 in print_int x
(seq (let (*match*/82 = (let (x/80 =[int] 1) (apply (field 43 (global Stdlib!)) x/80))) 0a) 0a)
このように(let (... =[int] ...) ...)
のような形にコンパイルされる。OCamlでは整数やcharは(他の多くのデータと違って)box化されないので、lambda以降のコンパイルで特別な扱いをできるよう=[int]
としているのだろうか?
モジュールレベル定義
最後にモジュールのトップレベルでの変数定義:
let x = 1 let y = 2 let () = print_int @@ x + y
(seq (let (x/80 =[int] 1) (setfield_ptr(root-init) 0 (global Local4!) x/80)) (let (y/81 =[int] 2) (setfield_ptr(root-init) 1 (global Local4!) y/81)) (let (*match*/85 = (apply (field 43 (global Stdlib!)) (+ (field 0 (global Local4!)) (field 1 (global Local4!))))) 0a) 0a)
OCamlコードのlet ...
が(seq ... 0a)
の中で(let ... ...)
に1対1で対応している。いったんx/80
のような変数に束縛した上で、(setfield_ptr(root-init) 0 (global Local4!) x/80)
などで現在定義中のモジュールのフィールドとして追加している。(このコードのファイル名はlocal4.ml
にしてあるのでモジュール名もLocal4
になる)
少し気になるのは、これlet
する意味があるのか?というところ。変数束縛した後に、その変数を使ってモジュールのフィールドに値を格納するだけなので、
(let (x/80 =[int] 1) (setfield_ptr(root-init) 0 (global Local4!) x/80))
を
(setfield_ptr(root-init) 0 (global Local4!) 1)
にしてしまっても差し支えない気もするのだが・・・ lambda IRの時点ではまだやっていない最適化なのか、それともlet
を経由することがメモリ上の意味を持つのか?Real World OCamlのRuntime関連の部分を熟読するとわかるのかもしれない。
次回
次は関数定義がどうlambda IRにコンパイルされるかを見ていく。