Arantium Maestum

プログラミング、囲碁、読書の話題

OCamlのlambda IRをいじる(変数)

前回に続いて、モジュール内で変数を定義して使うプログラムがどのようなlambda IRにコンパイルされるかを見ていく。

ローカル変数1つ

OCamlで変数を定義する場合、モジュールのトップレベルでの定義と、ある式の一部としてlet x = y in zxのような定義の二種類がある。

まずは後者から見ていく。

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