Chapter 8 バッチコンパイラ (ocamlc)

このページは最後に更新されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

last mod. 2008-10-01 (水) 17:42:36

この章では Objective Caml のバッチコンパイラである ocamlc について説明します。ocamlc はCaml のソースコードファイルをバイトコードオブジェクトにコンパイルし、これらのオブジェクトをリンクしてスタンドアロンのバイトコード実行ファイルを作ります。これらの実行ファイルはバイトコードインタプリタである ocamlrun によって実行されます。

コンパイラの概要

ocamlc コマンドは、多くの C コンパイラと似たコマンドラインインタフェースを持ちます。ocamlc はいくつかのタイプの引数を受け、それらを順番に処理します。

  • .mli で終わる引数はコンパイルユニットへのインタフェースのソースファイルとして扱われます。インタフェースはコンパイルユニットからエクスポートされる名前を指定します。インタフェースは、値の名前とその型の宣言、パブリックなデータ型の定義、抽象データ型の宣言などを行います。ocamlc はファイル x.mli なら、ファイル x.cmi を生成し、インターフェイスをコンパイルしたものを出力します。

  • .ml で終わる引数はコンパイルユニットの実装のソースファイルとして認識されます。実装はユニットが外部に公開する名前の定義を持ち、副作用のある式なども持ちます。ocamlc はファイル x.ml なら、ファイル x.cmo を生成し、コンパイル済みオブジェクトバイトコードを出力します。

インターフェイスファイル x.mli があったら、実装 x.ml は対応するコンパイル済みインターフェイスファイル x.cmi (これもあるはず) とチェックされます。インターフェイスファイル x.mli がなかったら、x.ml のコンパイルではオブジェクトコードファイル x.cmo だけでなくインターフェイスファイル x.cmi もコンパイル生成されます。その場合、x.cmi では実装 x.ml で定義されているものすべてを公開することになります。

  • .cmo で終わる引数はコンパイル済みオブジェクトバイトコードと認識されます。これらのファイルは (もしあれば) .ml 引数をコンパイルして得られたオブジェクトファイルや Objective Caml 標準ライブラリと一緒にリンクされて、単独実行ファイルが生成されます。コマンドラインでの .cmo や .ml の引数の順番には意味があります。コンパイルユニットはランタイム時その順で初期化され、初期化前のユニットのコンポーネントを使おうとしたらリンク時にエラーとなります。なのでファイル x.cmo はユニット x に参照を持つファイル .cmo より前に置かなければなりません。

  • .cma で終わる引数はオブジェクトバイトコードのライブラリと認識されます。オブジェクトバイトコードのライブラリは複数のオブジェクトバイトコードファイル (.cmo) を 1 つのファイルにまとめたものです。ライブラリは ocamlc -a で作成できます (-a オプションの説明は下の方を見て下さい) 。ライブラリ中のオブジェクトファイルは通常の .cmo と同じようにリンクされます (順番は .cma ファイル作成時に指定します) 。ただ 1 つ異なる点として、ライブラリ中のオブジェクトファイルでプログラム中のどこからも参照されないものはリンクされません。

  • .c で終わる引数は C コンパイラを通して .o オブジェクトファイルを生成します。このオブジェクトファイルは -custom フラグがセットされていたらプログラムにリンクされます (-custom の説明は下の方を見て下さい) 。

  • .o や .a (Windows では .obj や .lib) で終わる引数は C のオブジェクトファイルやライブラリとみなされます。-custom モードでリンクされているときこれらは C のリンカに通されます (-custom の説明は下の方を見て下さい) 。

  • .so (Windows では .dll) で終わる引数は C の共有ライブラリ (DLL) とみなされます。Caml コードから参照される外部 C 関数はリンク時にそこから検索され、その名前が生成されるバイトコードの実行ファイル中に出力されます。するとランタイムシステム ocamlrun はプログラムスタート時にそれを動的にロードします。

リンク段階で出力されるファイルはコンパイル済みバイトコードを含み、Objective Caml バイトコードインタプリタ ocamlrun の上で実行できます。出力されたファイル名が caml.out だとすると、以下のコマンドで caml.out 中のコンパイル済みコードを実行できます。

ocamlrun caml.out arg1 arg2 ... argn

文字列 arg1 から argn までが引数として実行ファイルに渡されます (詳細は 10 章を見てください) 。

多くのシステムでは出力されたファイルは以下のようにそのまま実行できます。

./caml.out arg1 arg2 ... argn

出力されたファイルは実行権限ビットがセットされていて、自力でバイトコードインタプリタを起動しようとします。

Options

ocamlc に認識されるコマンドラインオプションは以下の通りです。

  • -a

    コマンドラインに与えられたオブジェクトファイル (.cmo) をとり、ライブラリ (.cma) を作成します。実行ファイルは生成されません。ライブラリの名前は -o オプションで指定できます。

    コマンドラインに -custom 、-cclib や -ccopt オプションが渡されたら、これらのオプションは出力される .cma ライブラリに保存され、このライブラリをリンクするときに自動的に -custom 、-cclib や -ccopt が追加されます (ただしリンク時に -noautolink が指定されない場合)。

  • -c

    コンパイルのみ行います。リンクを行いません。ソースコードファイルをコンパイル済みファイルに変換しますが、実行ファイルは生成されません。このオプションはモジュールの分割コンパイル時に使用できます。

  • -cc ccomp

    ocamlc -custom で呼ばれる C リンカや .c ソースファイルをコンパイルする C コンパイラとして ccomp を使用します。

  • -cclib -llibname

    カスタムランタイムモードでのリンク時、C リンカにオプションとして -llibname を渡し、指定された C ライブラリをプログラムにリンクします。

  • -ccopt option

    カスタムランタイムモードでのリンク時、C コンパイラやリンカに与えられたオプションを渡します。例えば、-ccopt -Ldir なら C リンカは C ライブラリを探す際ディレクトリ dir を見るようになります。

  • -custom

    カスタムランタイムモードでリンクします。デフォルトのリンクモードでは、リンカは共有ランタイムシステム ocamlrun で使用するバイトコードを生成します。カスタムランタイムモードでは、リンカが生成するファイルにはランタイムシステムとプログラムのバイトコードが両方含まれます。よって生成されるファイルは大きくなりますが、直接実行できるので ocamlrun のない環境でも動作します。さらにカスタムランタイムモードでは Caml コードにユーザが定義した C 関数を静的にリンクできます。詳細は 18 章を見てください。

    Unix:

    ocamlc -custom で生成された実行ファイルに対して strip コマンドは使用しないで下さい。実行ファイルのバイトコード部分が削除されてしまいます。

  • -dllib -llibname

    ランタイムシステム ocamlrun がプログラムスタート時に動的ロードする C 共有ライブラリ dlllibname.so (Windows では dlllibname.dll) を指定します。

  • -dllpath dir

    ランタイムシステムが共有 C ライブラリを検索するディレクトリ dir を追加します。リンク時は共有ライブラリを標準検索パス (-I オプションと関連する) へ検索しにいきます。-dllpath オプションで dir が単純に生成される実行ファイルへ埋め込まで、ocamlrun がそれを見つけ探しに行きます。詳細は 10.3 章を見てください。

  • -dtypes

    詳細な型の情報を表示します。x.ml というファイルの情報は x.annot というファイルに出力されます。型エラーが起こった場合には、エラーが起きる前までに型チェッカによって推論されたすべての情報がダンプされます。x.annot ファイルは emacs/caml-types.el で定義されるコマンドで型をインタラクティブに表示するのに使うことができます。

  • -g

    コンパイルとリンク時にデバッグ情報を追加します。ocamldebug でプログラムをデバッグする場合はこのオプションが必要です。詳細は 16 章を見てください。また、プログラムが例外により終了した場合のスタックトレースを生成するためにも必要です。詳細は10.2節を見てください。

  • -i

    実装ファイル (.ml) をコンパイルする際、定義されている名前をすべて型と定義付きで表示します。コンパイル済みファイル(.cmo と .cmi)は生成されません。コンパイラがどのように型推論をしているかチェックするときに使用できます。また出力はインターフェイスの文法に従っているので、インターフェイスファイル (.mli) を書くときの手助けになります。標準出力を .mli ファイルにリダイレクトして、非公開な名前の宣言を消していくだけです。

  • -I directory

    コンパイル済みインターフェイスファイル (.cmi) 、コンパイル済みオブジェクトコードファイル (.cmo) 、ライブラリ (.cma) 、-cclib -lxxx で指定される C ライブラリを検索する際検索しに行くディレクトリのリストに与えられたディレクトリを追加します。デフォルトでは、まずカレントディレクトリが、それから標準ライブラリディレクトリが検索されます。-I で追加されたディレクトリは、カレントディレクトリより後に、標準ライブラリディレクトリより前に、コマンドラインに与えられた順に検索されます。

    + で始まるディレクトリ名は、標準ライブラリディレクトリからの相対パスとみなされます。例えば -I +labltk は標準ライブラリのサブディレクトリ labltk を検索パスに追加します。

  • -impl filename

    ファイル filename を実装ファイルとしてコンパイルします。拡張子が .ml でなくても構いません。

  • -intf filename

    ファイル filename をインターフェイスファイルとしてコンパイルします。拡張子が .mli でなくても構いません。

  • -linkall

    ライブラリに含まれるモジュールをすべて強制的にリンクします。このフラグが与えられなかったら、参照されないモジュールはリンクされません。ライブラリを作成するときに (-a flag) これを与えると、そのライブラリを使用するプログラムをリンクする際、ライブラリ内のモジュールはすべて強制的にリンクされます。

  • -make-runtime

    カスタムランタイムシステムを作成します。このランタイムシステムはコマンドラインで与えられた C オブジェクトやライブラリと合体しています。ocamlc -use-runtime runtime-name オプションで生成されたバイトコードがこのカスタムランタイムシステムを使用するようになります。詳しくはセクション 18.1.6 を見てください。

  • -noassert

    assertion のチェックをやめます。assertion はコンパイルされません。 すでにコンパイルされたファイルをリンクする場合には、このフラグは何の意味も持ちません。

  • -noautolink

    .cma ファイルをリンクするとき、ライブラリが潜在的に持つ -custom 、-cclib や -ccopt といったオプション (ライブラリを作成するときに指定されたもの) を無視します。ライブラリが間違った C ライブラリや C オプションの指定をしているときに使用できます。この場合、リンクの際コマンドラインで -noautolink を指定して、正しい C ライブラリやオプションを指定し直してください。

  • -nolabels

    型の中でオプションではないラベルを無視します。適用にラベルは使用できません。パラメータの順序が絶対になります。

  • -o exec-file

    リンカによって出力されるファイルの名前を指定します。デフォルトの出力ファイル名は Unix 環境で a.out、 Windows 環境で camlprog.exe です。-a が指定されている場合は生成されるライブラリ名の指定になります。もし -pack が指定されている場合は生成されるパックオブジェクトファイル名の指定になります。

-output-obj が指定されている場合は生成されるファイル名の指定になります。

  • -output-obj

    リンカは実行ファイルではなく C オブジェクトファイルを生成します。C のプログラムから呼べるように、Caml のコードを C のライブラリとしてラップするのに使います。18 章のセクション 18.7.5 を見てください。出力されるオブジェクトファイルの名前はデフォルトで camlprog.o です。-o オプションで変更可能です。

  • -pack

    コマンドラインで与えられたオブジェクトファイルをまとめて、オブジェクトファイル (.cmo) とそのコンパイル済みインターフェイス (.cmi) を生成します。元のオブジェクトは出力された .cmo ファイルのサブモジュールとなります。出力される .cmo ファイルの名前は -o オプションで必ず与えてください。

           ocamlc -pack -o p.cmo a.cmo b.cmo c.cmo

    例として、上記のコマンドは p.cmo と p.cmi を生成します。それらは 3 つのサブモジュール A 、B 、C を持ち、それぞれオブジェクトファイル a.cmo 、b.cmo 、c.cmo の内容に対応します。他のプログラムからは、P.A 、P.B 、P.C などとしてこれらの内容を参照することができます。

  • -pp command

    コンパイラは与えられたコマンド command をプリプロセッサとしてすべてのソースファイルに適用します。command の出力は一時ファイルにリダイレクトされてからコンパイルされます。コンパイルでエラーが発生しなかったら一時ファイルは削除されます。ソースファイルのベース名と拡張子 .ppi (インターフェイス .mli の場合) もしくは .ppo (実装 .ml の場合) から構成される名前が一時ファイルの名前になります。

  • -principal

    型チェックの際 information path をチェックします。すべての型が principal way で導出されているか確認します。ラベル付き引数や多相メソッドを使用しているときは、このフラグをつけてコンパイルの通らないコードは、将来コンパイラのバージョンアップで型チェックの内部アルゴリズムが変更された場合正しく型推論できなくなるかもしれません。 -principal モードでコンパイルの通るプログラムは必ずデフォルトモード (equivalent type) でもコンパイルが通ります (ただしバイナリの signature は異なります) 。このオプションを付けると型チェックが遅くなるかもしれません。ソースコードを公開する前に一度これでコンパイルしてみるといいです。

  • -rectypes

    型チェックの際、任意の再帰的な型を許します。デフォルトでは、オブジェクト型を経由する再帰による再帰的な型しかサポートされません。一度このフラグを使ってインターフェースファイルを生成したら、それが依存するすべてのものに対して使わなくてはいけません。

  • -thread

    24 章で解説される threads ライブラリと組み合わせてマルチスレッドプログラムをコンパイル、リンクします。

  • -unsafe

    配列や文字列アクセスのバウンダリチェックをオフにします (v.(i) や s.[i]) 。-unsafe でコンパイルされたプログラムはいくらか速くなりますが安全ではありません。プログラムが配列や文字列のバウンド外をアクセスした場合何が起こるかわかりません。

  • -use-runtime runtime-name

    あらかじめ ocamlc -make-runtime runtime-name で生成されているカスタムランタイムシステム runtime-name を使用して実行するバイトコード実行ファイルを生成します。 詳しくはセクション 18.1.6 を見てください。

  • -v

    コンパイラのバージョンと標準ライブラリディレクトリを表示して終了します。

  • -verbose

    外部コマンドを実行する前に、そのコマンドを表示します。特に -custom モードで C コンパイラやリンカの起動を表示します。C ライブラリプログラムをデバッグするのに使用できます。

  • -version

    コンパイラのバージョンを短い表記 (3.06 みたいに) 表示して終了します。

  • -vmthread

    24 章で解説される VM レベル threads ライブラリと組み合わせてマルチスレッドプログラムをコンパイル、リンクします。

  • -w warning-list

    警告を有効/無効にします。引数 warning-list は文字列で、各文字は以下の意味を持ちます。

    A/aすべての警告を有効/無効にする
    C/cコメントかどうか疑わしいものに対する警告を有効/無効にする
    D/d推奨されていない (deprecated) 機能に対する警告を有効/無効にする
    E/e不十分なパターンマッチ(バリアントにコンストラクタが追加されても、成立するmatch式)に対する警告を有効/無効にする
    F/f関数の部分適用に対する警告を有効/無効にする (つまり f x; expr のようなコードで、適用 f x が関数型を持つようなとき).
    L/l適用でラベルが省略されていることに対する警告を有効/無効にする
    M/mメソッドのオーバーライドに対する警告を有効/無効にする
    P/p部分マッチ (パターンマッチでどれにもマッチしない場合があるもの) に対する警告を有効/無効にする
    S/senable/disable warnings for statements that do not have type unit (e.g. expr1; expr2 when expr1 does not have type unit), and that are not functions (covered by warning F).
    U/u使用されない (冗長な) マッチケースに対するに対する警告を有効/無効にする
    V/v隠れたインスタンス変数に対する警告を有効/無効にする
    Y/yenable/disable warnings for unused variables bound with the let or as keywords and that don't start with an underscore.
    Z/zenable/disable warnings for all other unused variables that don't start with an underscore.
    X/xenable/disable warnings that don't fit in the above categories.

The default setting is -w Aelz (all warnings enabled except fragile matchings, omitted labels, non-suspicious unused variables). Warnings F and

S rely on the type system, thus they are not triggered when the type of the statement is unknown.

  • warn-error warning-list Turn the warnings indicated in the argument warning-list into errors. The compiler will stop with an error when one of these warnings is emitted. The warning-list is a string of one or several characters, with the same meaning as for the -w option: an uppercase character turns the corresponding warning into an error, a lowercase character leaves it as a warning. The default setting is -warn-error a (none of the warnings is treated as an error).

  • where Print the location of the standard library, then exit.

Modules and the file system

This short section is intended to clarify the relationship between the names of the modules corresponding to compilation units and the names of the files that contain their compiled interface and compiled implementation.

The compiler always derives the module name by taking the capitalized base name of the source file (.ml or .mli file). That is, it strips the leading directory name, if any, as well as the .ml or .mli suffix; then, it set the first letter to uppercase, in order to comply with the requirement that module names must be capitalized. For instance, compiling the file mylib/misc.ml provides an implementation for the module named Misc. Other compilation units may refer to components defined in mylib/misc.ml under the names Misc.name; they can also do open Misc, then use unqualified names name.

The .cmi and .cmo files produced by the compiler have the same base name as the source file. Hence, the compiled files always have their base name equal (modulo capitalization of the first letter) to the name of the module they describe (for .cmi files) or implement (for .cmo files).

When the compiler encounters a reference to a free module identifier Mod, it looks in the search path for a file named Mod.cmi or mod.cmi and loads the compiled interface contained in that file. As a consequence, renaming .cmi files is not advised: the name of a .cmi file must always correspond to the name of the compilation unit it implements. It is admissible to move them to another directory, if their base name is preserved, and the correct -I options are given to the compiler. The compiler will flag an error if it loads a .cmi file that has been renamed.

Compiled bytecode files (.cmo files), on the other hand, can be freely renamed once created. That's because the linker never attempts to find by itself the .cmo file that implements a module with a given name: it relies instead on the user providing the list of .cmo files by hand.

Common errors

This section describes and explains the most frequently encountered error messages.

Cannot find file filename The named file could not be found in the current directory, nor in the directories of the search path. The filename is either a compiled interface file (.cmi file), or a compiled bytecode file (.cmo file). If filename has the format mod.cmi, this means you are trying to compile a file that references identifiers from module mod, but you have not yet compiled an interface for module mod. Fix: compile mod.mli or mod.ml first, to create the compiled interface mod.cmi.

If filename has the format mod.cmo, this means you are trying to link a bytecode object file that does not exist yet. Fix: compile mod.ml first.

If your program spans several directories, this error can also appear because you haven't specified the directories to look into. Fix: add the correct -I options to the command line.

Corrupted compiled interface filename The compiler produces this error when it tries to read a compiled interface file (.cmi file) that has the wrong structure. This means something went wrong when this .cmi file was written: the disk was full, the compiler was interrupted in the middle of the file creation, and so on. This error can also appear if a .cmi file is modified after its creation by the compiler. Fix: remove the corrupted .cmi file, and rebuild it.

This expression has type t_1, but is used with type t_2 This is by far the most common type error in programs. Type t_1 is the type inferred for the expression (the part of the program that is displayed in the error message), by looking at the expression itself. Type t_2 is the type expected by the context of the expression; it is deduced by looking at how the value of this expression is used in the rest of the program. If the two types t_1 and t_2 are not compatible, then the error above is produced.

In some cases, it is hard to understand why the two types t_1 and t_2 are incompatible. For instance, the compiler can report that "expression of type foo cannot be used with type foo", and it really seems that the two types foo are compatible. This is not always true. Two type constructors can have the same name, but actually represent different types. This can happen if a type constructor is redefined. Example:

type foo = A | B

            let f = function A -> 0 | B -> 1
            type foo = C | D
            f C

This result in the error message "expression C of type foo cannot be used with type foo".

The type of this expression, t, contains type variables that cannot be generalized Type variables ('a, 'b, ...) in a type t can be in either of two states: generalized (which means that the type t is valid for all possible instantiations of the variables) and not generalized (which means that the type t is valid only for one instantiation of the variables). In a let binding let name = expr, the type-checker normally generalizes as many type variables as possible in the type of expr. However, this leads to unsoundness (a well-typed program can crash) in conjunction with polymorphic mutable data structures. To avoid this, generalization is performed at let bindings only if the bound expression expr belongs to the class of "syntactic values", which includes constants, identifiers, functions, tuples of syntactic values, etc. In all other cases (for instance, expr is a function application), a polymorphic mutable could have been created and generalization is therefore turned off for all variables occuring in contravariant or non-variant branches of the type. For instance, if the type of a non-value is 'a list the variable is generalizable (list is a covariant type constructor), but not in 'a list -> 'a list (the left branch of -> is contravariant) or 'a ref (ref is non-variant).

Non-generalized type variables in a type cause no difficulties inside a given structure or compilation unit (the contents of a .ml file, or an interactive session), but they cannot be allowed inside signatures nor in compiled interfaces (.cmi file), because they could be used inconsistently later.

Therefore, the compiler flags an error when a structure or compilation unit defines a value name whose type contains non-generalized type variables.

There are two ways to fix this error:

  • Add a type constraint or a .mli file to give a monomorphic type (without type variables) to name. For instance, instead of writing
    let sort_int_list = Sort.list (<)
    (* inferred type 'a list -> 'a list, with 'a not generalized *)
    write
    let sort_int_list = (Sort.list (<) : int list -> int list);;
  • If you really need name to have a polymorphic type, turn its defining expression into a function by adding an extra parameter. For instance, instead of writing
    let map_length = List.map Array.length
    (* inferred type 'a array list -> int list, with 'a not generalized *)
    write
    let map_length lv = List.map Array.length lv

Reference to undefined global mod

このエラーは不完全または不正確な順番でファイルをリンクしようとしたとき、または mod という名前のコンパイルユニットの実装 (普通このファイルは mod.cmo という名前か、ライブラリ中にそういう名前で入っているかのどちらか) をコマンドラインに指定し忘れていたときに発生します。.ml か .cmo ファイルをコマンドラインに追加してください。mod という名前のモジュールを含む実装をちゃんとコマンドラインに指定している場合は、コマンドライン上の位置が後ろすぎるのでしょう。mod の実装は mod を参照するバイトコードオブジェクトファイルのどれよりも前にないといけません。コマンドライン上の .ml か .cmo ファイルの順番を適当に変えてください。

このエラーはモジュール間で相互再帰する関数を作ると当然発生します。つまり関数 Mod1.f が関数 Mod2.g を呼び、かつ関数 Mod2.g が関数 Mod1.f を呼ぶときです。この場合、コマンドラインの順番をどのように変えてもプログラムはリンク時に拒否されます。対策として以下のようにしてください。

  • 関数 f と g は同じモジュールに置く
  • 片方から関数をパラメータ化する。 つまり、次のような例は
    mod1.ml:    let f x = ... Mod2.g ...
    mod2.ml:    let g y = ... Mod1.f ...
    以下のように定義します。
    mod1.ml:    let f g x = ... g ...
    mod2.ml:    let rec g y = ... Mod1.f g ...
    リンクは mod1.cmo を mod2.cmo より前にしてください。
  • 片方の関数を持つ参照を使う
    mod1.ml:    let forward_g =
                    ref((fun x -> failwith "forward_g") : <type>)
                let f x = ... !forward_g ...
    mod2.ml:    let g y = ... Mod1.f ...
                let _ = Mod1.forward_g := g

The external function f is not available

このエラーは C で書かれた外部関数を呼ぶコードをリンクするとき発生します。18 章で説明するように、f という C 関数を使用する実装は C ライブラリとリンクしなければいけません。C ライブラリが共有ライブラリ (DLL) でない場合は、カスタムランタイムモードでリンクしなければなりません。コマンドラインに使用する C ライブラリを追加して、必要なら -custom オプションも追加してください。

新規 編集 添付