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にコンパイルされるかを見ていく。