yukiのブログ

作ったものなど

MinCamlをjbuilderでビルドする

この記事はIS17er Advent Calendar 2017 - Adventarの13日目の記事です。

jbuilderOCaml用のビルドツールで、OCamlbuildOMake, OASISの親戚です。jbuilderを使うとOCamlを使って書いたプログラムのビルドが楽にできます。

MinCamljbuilderでビルドするのを試したところうまく設定できたので、この記事ではその方法を書きます。

フォークレポジトリはこれです。

github.com

下準備

MinCamlソースコードをダウンロードし、 ./to_x86 というスクリプトを実行しておきます。

このコマンドがないとそもそもビルドができません。

jbuilderのインストール

opamから行えます。

opam install jbuilder

jbuild の記述

OCamlMakefileを使うのに Makefile が必要なように、jbuilderを使うには jbuild が必要です。

MinCamlと同じディレクトリに jbuild というファイルを作り、以下を記述します。 このファイルの書き方はかなり特殊だと思うので、ドキュメントを参照してください(自分でもこれであってるのかイマイチ自信がありません...)。

(jbuild_version 1)

;lexer, parser はモジュール名
(ocamllex (lexer))
(ocamlyacc (parser))

(library ; 実行形式以外のモジュールを詰めたライブラリを作る
 (; トップモジュールの名前はMinCaml
  (name           MinCaml)
  ; MinCamlモジュールに詰めるモジュールを書く
  ; 読み込むと入力を受け付け始めるMain, Anchor以外すべて
  ; ":standard"でこのフォルダにあるモジュールを表し、 "\"以降で除外するモジュールを選択している
  (modules        (:standard \ main anchor))
  ; 依存している外部ライブラリ
  (libraries      (str))
  ; cのスタブの名前(float.c)
  (c_names         (float))))

(executable ; 実行形式
 (; 実行形式の名前
  (name           main)
  ; Mainモジュールのみをコンパイルする
  (modules        Main)
  ; 依存ライブラリ: 上で定義したMinCamlモジュール
  (libraries      (MinCaml))))

各モジュール間の依存関係を書く必要はなく勝手に推論してくれます。もし依存関係にループがあった場合はどのようにループしているかを示してくれます。そのため新しいファイルを後から追加しても設定の変更は必要ありません。

なお後述する jbuilder utop という機能を利用するために、「 main.mlanchor.ml 以外のファイルをコンパイルしてライブラリ MinCaml を作り、そのライブラリを使って main.mlコンパイルする」という、多少遠回りな方法を取りました。

main.mlを編集する

main.ml が (同じディレクトリにある*.mlファイルで定義されるモジュールではなく) MinCaml モジュール内のモジュールを使うように、 冒頭に open MinCaml を追加します。

open MinCaml (* 追加 *)

(* 以下は同じ *)
let limit = ref 1000

...

ビルド

以上の設定でビルドができるようになります。次のコマンドでビルドします。

jbuilder build main.exe

これで main.mlコンパイルしたファイル ./_build/default/main.exe が作られます。

jubuilder utop

jubuilder utop コマンドを実行すると、そのフォルダの jbuild ファイルで定義されたライブラリのビルドが行われ、そのライブラリをロードしたutopが起動します。

上のように jbuild を記述した上で jbuilder utop を実行すると以下のようになります。

f:id:e-yuki67:20171213205657p:plain

ライブラリのロードが行われるので、 モジュール名 (画像ではMinCaml) を打つと補完が効きます。ソースコードの修正とデバッグを繰り返すときに便利です。

main.mlコンパイルするのに MinCaml モジュールををわざわざ作った理由は、 Main モジュールがutopから読み込まれると標準入力を読み込み始めてしまうので除外する必要があったためです。

Makefileと組み合わせる

もともとのMinCamlMakefileを使ってテストをするようになっているので、jbuilderと結合させます。

RESULT = min-caml
SRC   = $(shell ls *.ml *.mli)
TESTS = $(shell ls test/*.ml | grep -v toomanyargs)
JBUILD_BUILD_PATH=./_build/default

default: $(RESULT)

# jbuilderを使ったビルド
$(RESULT): $(JBUILD_BUILD_PATH)/main.exe $(SRC)
        cp $(JBUILD_BUILD_PATH)/main.exe $(RESULT)

$(JBUILD_BUILD_PATH)/main.exe: $(SRC)
        jbuilder build main.exe

# モジュールをすべてロードしたutopを起動する
utop:
        jbuilder utop

# 以下同じ
do_test: $(TESTS:%.ml=%.cmp)
...

これで make, make do_test が元のMinCamlと同じ意味で使えるようになり、 make utop でライブラリをロードしたutopが起動するようになります。

jbuilderの紹介はここまでです。

その他OCaml小ネタ

思いついたものを書きます。

install_printer

ocamlのREPLで #install_printer hoge とするとそれ以降プリント関数として使うことができます。

具体的には

# type foo = A | B;;
# let print_foo fmt = function ;; 型fooのプリント関数
  | A -> Format.fprintf fmt "this is value A"
  | B -> Format.fprintf fmt "this is value B";;
val print_foo : Format.formatter -> foo -> unit = <fun>
# A;;  ; print_installする前
- : foo = A
# B;;
- : foo = B
# #install_printer print_foo;;
# A;;  ;; print_installした後
- : foo = this is value A
# B;;
- : foo = this is value B
# (A, B);;  ;; 組み合わさっていてもOK
- : foo * foo = (this is value A, this is value B)

とします。

なおプリント関数の型が t -> unit であっても動きますが、これは deprecated だそうです。(ソース)

バックトレースの有効化

REPLで Printexc.record_backtrace true とすると例外が出たときにどこから出たのか教えてくれます。

これがあればコンパイラをいじった結果 Exception: Assert_failure が全く予期しないところから生じても大丈夫です。

.ocamlinit

ocamlのREPLは立ち上がるときに同じディレクトリにある .ocamlinit を読んで実行するので、ここに毎回実行する処理を書いておくと楽です。

自分が作業するMinCamlでは

open MinCaml;;
(* エラーが出たときのバックトレースの有効化 *)
Printexc.record_backtrace true;;
(* プリント関数の有効化*)
#install_printer Syntax.print
#install_printer KNormal.print
#install_printer Closure.print
#install_printer Asm.print

と書いています。

flambdaの有効化

flambda とはOCamlのネイティブコンパイラが持つ最適化機能の総称です。デフォルトの状態では(たぶん)有効化されないので、以下のコマンドで有効化されたコンパイラをビルドする必要があります。

opam switch 4.05.0+flambda

これで -O3 オプションを付けてコンパイルすれば最適化が有効になります。

自分が書いたシミュレータでは40%程度高速化しました。



以上です。